diff --git a/buildtools/wafsamba/stale_files.py b/buildtools/wafsamba/stale_files.py
index 2dd08e1..0afbfe4 100644
--- a/buildtools/wafsamba/stale_files.py
+++ b/buildtools/wafsamba/stale_files.py
@@ -88,7 +88,7 @@ def replace_refill_task_list(self):
link = os.readlink(p)
if link[0:bin_base_len] == bin_base:
p = link
- if f in ['config.h']:
+ if f in ['config.h', 'vfs_virusfilter_config.h']:
continue
(froot, fext) = os.path.splitext(f)
if fext not in [ '.c', '.h', '.so', '.o' ]:
diff --git a/docs-xml/manpages/vfs_virusfilter_clamav.8.xml b/docs-xml/manpages/vfs_virusfilter_clamav.8.xml
new file mode 100644
index 0000000..2483368
--- /dev/null
+++ b/docs-xml/manpages/vfs_virusfilter_clamav.8.xml
@@ -0,0 +1,295 @@
+
+
+
+
+
+ vfs_virusfilter_clamav
+ 8
+ Samba
+ System Administration tools
+ 4.4
+
+
+
+
+ vfs_virusfilter_clamav
+ On access virus scanner
+
+
+
+
+ vfs objects = virusfilter_clamav
+
+
+
+
+ DESCRIPTION
+
+ This is a set of various Samba VFS modules to scan and filter
+ virus files on Samba file services with an anti-virus scanner.
+
+ This module is stackable.
+
+
+
+
+ OPTIONS
+
+
+
+
+ virusfilter_clamav:socket path = PATH
+
+ Path of local socket for the virus scanner.
+
+ If this option is not set, the default path /var/run/clamav/clamd.ctl.
+
+
+
+
+ virusfilter_clamav:connect timeout = 30000
+
+ Controls how long to wait on connecting to the virus
+ scanning process before timing out. Value is in milliseconds.
+
+ If this option is not set, the default is 30000.
+
+
+
+
+ virusfilter_clamav:io timeout = 60000
+
+ Controls how long to wait on communications with the virus
+ scanning process before timing out. Value is in milliseconds.
+
+ If this option is not set, the default is 60000.
+
+
+
+
+ virusfilter_clamav:scan on open = yes
+
+ This option controls whether files are scanned on open.
+
+ If this option is not set, the default is yes.
+
+
+
+
+ virusfilter_clamav:scan on close = no
+
+ This option controls whether files are scanned on open.
+
+ If this option is not set, the default is no.
+
+
+
+
+ virusfilter_clamav:max file size = 100000000
+
+ This is the largest sized file, in bytes, which will be scanned.
+
+ If this option is not set, the default is 100MB.
+
+
+
+
+ virusfilter_clamav:min file size = 10
+
+ This is the largest sized file, in bytes, which will be scanned.
+
+ If this option is not set, the default is 0.
+
+
+
+
+ virusfilter_clamav:infected file action = nothing
+
+ What to do with an infected file. The options are
+ nothing, quarantine, rename, delete. Rename is alias for quarantine
+ with keep tree and keep name both set to yes. I.e. rename is a
+ quarantine in place.
+ If this option is not set, the default is nothing.
+
+
+
+
+ virusfilter_clamav:infected file errno on close = 0
+
+ What errno to return on close if the file is infected.
+
+ If this option is not set, the default is 0.
+
+
+
+
+ virusfilter_clamav:infected file errno on open = EACCES
+
+ What errno to return on close if the file is infected.
+
+ If this option is not set, the default is EACCES.
+
+
+
+
+ virusfilter_clamav:quarantine directory = PATH
+
+ Where to move infected files.
+
+
+
+
+ virusfilter_clamav:quarantine prefix = virusfilter.
+
+ Prefix for quarantined files.
+ If this option is not set, the default is "virusfilter.".
+
+
+
+
+ virusfilter_clamav:quarantine suffix = .infected
+
+ Suffix for quarantined files.
+ This option is only used if keep name is true. Otherwise it is ignored.
+ If this option is not set, the default is ".infected".
+
+
+
+
+ virusfilter_clamav:rename prefix = virusfilter.
+
+ Prefix for infected files.
+ If this option is not set, the default is "virusfilter.".
+
+
+
+
+ virusfilter_clamav:rename suffix = .infected
+
+ Suffix for infected files.
+ If this option is not set, the default is ".infected".
+
+
+
+
+ virusfilter_clamav:quarantine keep tree = yes
+
+ If keep tree is set, the directory structure relative
+ to the share is maintained in the quarantine directory.
+
+ If this option is not set, the default is no.
+
+
+
+
+ virusfilter_clamav:quarantine keep name = yes
+
+ Should the file name be left unmodified other than adding a suffix
+ and/or prefix and a random suffix name as defined in virusfilter_fsav:rename prefix
+ and virusfilter_fsav:rename suffix.
+ If this option is not set, the default is no.
+
+
+
+
+ virusfilter_clamav: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: "
+
+ External command to run on an infected file is found.
+ If this option is not set, the default is none.
+
+
+
+
+ virusfilter_clamav:scan error command = @SAMBA_DATADIR@/bin/virusfilter-notify --mail-to virusmaster@example.com --from samba@example.com --subject-prefix "Samba: Scan Error: "
+
+ External command to run on scan error.
+ If this option is not set, the default is none.
+
+
+
+
+ virusfilter_clamav:exclude files = empty
+
+ Files to exclude from scanning.
+ If this option is not set, the default is empty.
+
+
+
+
+ virusfilter_clamav:block access on error = false
+
+ Controls whether or not access should be blocked on
+ a scanning error.
+ If this option is not set, the default is false.
+
+
+
+
+ virusfilter_clamav:scan error errno on close = 0
+
+ What errno to return on close if there is an error in
+ scanning the file and block access on error is true.
+
+ If this option is not set, the default is 0.
+
+
+
+
+ virusfilter_clamav:scan error errno on open = EACCES
+
+ What errno to return on open if there is an error in
+ scanning the file and block access on error is true.
+
+ If this option is not set, the default is EACCES.
+
+
+
+
+ virusfilter_clamav:cache entry limit = 100
+
+ The maximum number of entries in the scanning results
+ cache. Due to how Samba's memcache works, this is approximate.
+ If this option is not set, the default is 100.
+
+
+
+
+ virusfilter_clamav:cache time limit = 10
+
+ The maximum number of seconds that a scanning result
+ will stay in the results cache. -1 disables the limit.
+ 0 disables caching.
+ If this option is not set, the default is 10.
+
+
+
+
+ virusfilter_clamav:quarantine directory mode = 700
+
+ This is the octet mode for the quarantine directory and
+ its subdirectories as they are created.
+ If this option is not set, the default is 700 or S_IRUSR | S_IWUSR | S_IXUSR.
+
+
+
+
+
+
+
+ VERSION
+
+ This man page is correct for version 4.0 of the Samba suite.
+
+
+
+
+ AUTHOR
+
+ 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.
+
+
+
+
diff --git a/docs-xml/manpages/vfs_virusfilter_fsav.8.xml b/docs-xml/manpages/vfs_virusfilter_fsav.8.xml
new file mode 100644
index 0000000..a9171e6
--- /dev/null
+++ b/docs-xml/manpages/vfs_virusfilter_fsav.8.xml
@@ -0,0 +1,319 @@
+
+
+
+
+
+ vfs_virusfilter_fsav
+ 8
+ Samba
+ System Administration tools
+ 4.4
+
+
+
+
+ vfs_virusfilter_fsav
+ On access virus scanner
+
+
+
+
+ vfs objects = virusfilter_fsav
+
+
+
+
+ DESCRIPTION
+
+ This is a set of various Samba VFS modules to scan and filter
+ virus files on Samba file services with an anti-virus scanner.
+
+ This module is stackable.
+
+
+
+
+ OPTIONS
+
+
+
+
+ virusfilter_fsav:socket path = PATH
+
+ Path of local socket for the virus scanner.
+
+ If this option is not set, the default path /tmp/.fsav-0.
+
+
+
+
+ virusfilter_fsav:connect timeout = 30000
+
+ Controls how long to wait on connecting to the virus
+ scanning process before timing out. Value is in milliseconds.
+
+ If this option is not set, the default is 30000.
+
+
+
+
+ virusfilter_fsav:io timeout = 60000
+
+ Controls how long to wait on communications with the virus
+ scanning process before timing out. Value is in milliseconds.
+
+ If this option is not set, the default is 60000.
+
+
+
+
+ virusfilter_fsav:scan on open = yes
+
+ This option controls whether files are scanned on open.
+
+ If this option is not set, the default is yes.
+
+
+
+
+ virusfilter_fsav:scan on close = no
+
+ This option controls whether files are scanned on open.
+
+ If this option is not set, the default is no.
+
+
+
+
+ virusfilter_fsav:max file size = 100000000
+
+ This is the largest sized file, in bytes, which will be scanned.
+
+ If this option is not set, the default is 100MB.
+
+
+
+
+ virusfilter_fsav:min file size = 10
+
+ This is the largest sized file, in bytes, which will be scanned.
+
+ If this option is not set, the default is 0.
+
+
+
+
+ virusfilter_fsav:infected file action = nothing
+
+ What to do with an infected file. The options are
+ nothing, quarantine, rename, delete. Rename is alias for quarantine
+ with keep tree and keep name both set to yes. I.e. rename is a
+ quarantine in place.
+ If this option is not set, the default is nothing.
+
+
+
+
+ virusfilter_fsav:infected file errno on close = 0
+
+ What errno to return on close if the file is infected.
+
+ If this option is not set, the default is 0.
+
+
+
+
+ virusfilter_fsav:infected file errno on open = EACCES
+
+ What errno to return on close if the file is infected.
+
+ If this option is not set, the default is EACCES.
+
+
+
+
+ virusfilter_fsav:quarantine directory = PATH
+
+ Where to move infected files.
+
+
+
+
+ virusfilter_fsav:quarantine prefix = virusfilter.
+
+ Prefix for quarantined files.
+ If this option is not set, the default is "virusfilter.".
+
+
+
+
+ virusfilter_fsav:quarantine suffix = .infected
+
+ Suffix for quarantined files.
+ This option is only used if keep name is true. Otherwise it is ignored.
+ If this option is not set, the default is ".infected".
+
+
+
+
+ virusfilter_fsav:rename prefix = virusfilter.
+
+ Prefix for infected files.
+ If this option is not set, the default is "virusfilter.".
+
+
+
+
+ virusfilter_fsav:rename suffix = .infected
+
+ Suffix for infected files.
+ If this option is not set, the default is ".infected".
+
+
+
+
+ virusfilter_fsav:quarantine keep tree = yes
+
+ If keep tree is set, the directory structure relative
+ to the share is maintained in the quarantine directory.
+
+ If this option is not set, the default is no.
+
+
+
+
+ virusfilter_fsav:quarantine keep name = yes
+
+ Should the file name be left unmodified other than adding a suffix
+ and/or prefix and a random suffix name as defined in virusfilter_fsav:rename prefix
+ and virusfilter_fsav:rename suffix.
+ If this option is not set, the default is no.
+
+
+
+
+ virusfilter_fsav: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: "
+
+ External command to run on an infected file is found.
+ If this option is not set, the default is none.
+
+
+
+
+ virusfilter_fsav:scan archive = true
+
+ This defines whether or not to scan archives.
+ The f-sav module supports this and defaults to false.
+
+
+
+
+ virusfilter_fsav:max nested scan archive = 1
+
+ This defines the maximum depth to search nested archives.
+ f-secure module supports this and defaults to 1.
+
+
+
+
+ virusfilter_fsav:scan mime = true
+
+ This defines whether or not to scan mime files.
+ f-secure module supports this and defaults to false.
+
+
+
+
+ virusfilter_fsav:scan error command = @SAMBA_DATADIR@/bin/virusfilter-notify --mail-to virusmaster@example.com --from samba@example.com --subject-prefix "Samba: Scan Error: "
+
+ External command to run on scan error.
+ If this option is not set, the default is none.
+
+
+
+
+ virusfilter_fsav:exclude files = empty
+
+ Files to exclude from scanning.
+ If this option is not set, the default is empty.
+
+
+
+
+ virusfilter_fsav:block access on error = false
+
+ Controls whether or not access should be blocked on
+ a scanning error.
+ If this option is not set, the default is false.
+
+
+
+
+ virusfilter_fsav:scan error errno on close = 0
+
+ What errno to return on close if there is an error in
+ scanning the file and block access on error is true.
+
+ If this option is not set, the default is 0.
+
+
+
+
+ virusfilter_fsav:scan error errno on open = EACCES
+
+ What errno to return on open if there is an error in
+ scanning the file and block access on error is true.
+
+ If this option is not set, the default is EACCES.
+
+
+
+
+ virusfilter_fsav:cache entry limit = 100
+
+ The maximum number of entries in the scanning results
+ cache. Due to how Samba's memcache works, this is approximate.
+ If this option is not set, the default is 100.
+
+
+
+
+ virusfilter_fsav:cache time limit = 10
+
+ The maximum number of seconds that a scanning result
+ will stay in the results cache. -1 disables the limit.
+ 0 disables caching.
+ If this option is not set, the default is 10.
+
+
+
+
+ virusfilter_fsav:quarantine directory mode = 700
+
+ This is the octet mode for the quarantine directory and
+ its subdirectories as they are created.
+ If this option is not set, the default is 700 or S_IRUSR | S_IWUSR | S_IXUSR.
+
+
+
+
+
+
+
+ VERSION
+
+ This man page is correct for version 4.0 of the Samba suite.
+
+
+
+
+ AUTHOR
+
+ 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.
+
+
+
+
diff --git a/docs-xml/manpages/vfs_virusfilter_sophos.8.xml b/docs-xml/manpages/vfs_virusfilter_sophos.8.xml
new file mode 100644
index 0000000..34f783a
--- /dev/null
+++ b/docs-xml/manpages/vfs_virusfilter_sophos.8.xml
@@ -0,0 +1,312 @@
+
+
+
+
+
+ vfs_virusfilter_sophos
+ 8
+ Samba
+ System Administration tools
+ 4.4
+
+
+
+
+ vfs_virusfilter_sophos
+ On access virus scanner
+
+
+
+
+ vfs objects = virusfilter_sophos
+
+
+
+
+ DESCRIPTION
+
+ This is a set of various Samba VFS modules to scan and filter
+ virus files on Samba file services with an anti-virus scanner.
+
+ This module is stackable.
+
+
+
+
+ OPTIONS
+
+
+
+
+ virusfilter_sophos:socket path = PATH
+
+ Path of local socket for the virus scanner.
+
+ If this option is not set, the default path /var/run/savdi/sssp.sock.
+
+
+
+
+
+ virusfilter_sophos:connect timeout = 30000
+
+ Controls how long to wait on connecting to the virus
+ scanning process before timing out. Value is in milliseconds.
+
+ If this option is not set, the default is 30000.
+
+
+
+
+ virusfilter_sophos:io timeout = 60000
+
+ Controls how long to wait on communications with the virus
+ scanning process before timing out. Value is in milliseconds.
+
+ If this option is not set, the default is 60000.
+
+
+
+
+ virusfilter_sophos:scan on open = yes
+
+ This option controls whether files are scanned on open.
+
+ If this option is not set, the default is yes.
+
+
+
+
+ virusfilter_sophos:scan on close = no
+
+ This option controls whether files are scanned on open.
+
+ If this option is not set, the default is no.
+
+
+
+
+ virusfilter_sophos:max file size = 100000000
+
+ This is the largest sized file, in bytes, which will be scanned.
+
+ If this option is not set, the default is 100MB.
+
+
+
+
+ virusfilter_sophos:min file size = 10
+
+ This is the largest sized file, in bytes, which will be scanned.
+
+ If this option is not set, the default is 0.
+
+
+
+
+ virusfilter_sophos:infected file action = nothing
+
+ What to do with an infected file. The options are
+ nothing, quarantine, rename, delete. Rename is alias for quarantine
+ with keep tree and keep name both set to yes. I.e. rename is a
+ quarantine in place.
+ If this option is not set, the default is nothing.
+
+
+
+
+ virusfilter_sophos:infected file errno on close = 0
+
+ What errno to return on close if the file is infected.
+
+ If this option is not set, the default is 0.
+
+
+
+
+ virusfilter_sophos:infected file errno on open = EACCES
+
+ What errno to return on close if the file is infected.
+
+ If this option is not set, the default is EACCES.
+
+
+
+
+ virusfilter_sophos:quarantine directory = PATH
+
+ Where to move infected files.
+
+
+
+
+ virusfilter_sophos:quarantine prefix = virusfilter.
+
+ Prefix for quarantined files.
+ If this option is not set, the default is "virusfilter.".
+
+
+
+
+ virusfilter_sophos:quarantine suffix = .infected
+
+ Suffix for quarantined files.
+ This option is only used if keep name is true. Otherwise it is ignored.
+ If this option is not set, the default is ".infected".
+
+
+
+
+ virusfilter_sophos:rename prefix = virusfilter.
+
+ Prefix for infected files.
+ If this option is not set, the default is "virusfilter.".
+
+
+
+
+ virusfilter_sophos:rename suffix = .infected
+
+ Suffix for infected files.
+ If this option is not set, the default is ".infected".
+
+
+
+
+ virusfilter_sophos:quarantine keep tree = yes
+
+ If keep tree is set, the directory structure relative
+ to the share is maintained in the quarantine directory.
+
+ If this option is not set, the default is no.
+
+
+
+
+ virusfilter_sophos:quarantine keep name = yes
+
+ Should the file name be left unmodified other than adding a suffix
+ and/or prefix and a random suffix name as defined in virusfilter_sophos:rename prefix
+ and virusfilter_sophos:rename suffix.
+ If this option is not set, the default is no.
+
+
+
+
+ virusfilter_sophos: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: "
+
+ External command to run on an infected file is found.
+ If this option is not set, the default is none.
+
+
+
+
+ virusfilter_sophos:scan archive = true
+
+ This defines whether or not to scan archives.
+ Sophos supports this and defaults to false.
+
+
+
+
+ virusfilter_sophos:max nested scan archive = 1
+
+ This defines the maximum depth to search nested archives.
+ The Sophos module supports this and defaults to 1.
+
+
+
+
+ virusfilter_sophos:scan error command = @SAMBA_DATADIR@/bin/virusfilter-notify --mail-to virusmaster@example.com --from samba@example.com --subject-prefix "Samba: Scan Error: "
+
+ External command to run on scan error.
+ If this option is not set, the default is none.
+
+
+
+
+ virusfilter_sophos:exclude files = empty
+
+ Files to exclude from scanning.
+ If this option is not set, the default is empty.
+
+
+
+
+ virusfilter_sophos:block access on error = false
+
+ Controls whether or not access should be blocked on
+ a scanning error.
+ If this option is not set, the default is false.
+
+
+
+
+ virusfilter_sophos:scan error errno on close = 0
+
+ What errno to return on close if there is an error in
+ scanning the file and block access on error is true.
+
+ If this option is not set, the default is 0.
+
+
+
+
+ virusfilter_sophos:scan error errno on open = EACCES
+
+ What errno to return on open if there is an error in
+ scanning the file and block access on error is true.
+
+ If this option is not set, the default is EACCES.
+
+
+
+
+ virusfilter_sophos:cache entry limit = 100
+
+ The maximum number of entries in the scanning results
+ cache. Due to how Samba's memcache works, this is approximate.
+ If this option is not set, the default is 100.
+
+
+
+
+ virusfilter_sophos:cache time limit = 10
+
+ The maximum number of seconds that a scanning result
+ will stay in the results cache. -1 disables the limit.
+ 0 disables caching.
+ If this option is not set, the default is 10.
+
+
+
+
+ virusfilter_sophos:quarantine directory mode = 700
+
+ This is the octet mode for the quarantine directory and
+ its subdirectories as they are created.
+ If this option is not set, the default is 700 or S_IRUSR | S_IWUSR | S_IXUSR.
+
+
+
+
+
+
+
+ VERSION
+
+ This man page is correct for version 4.0 of the Samba suite.
+
+
+
+
+ AUTHOR
+
+ 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.
+
+
+
+
diff --git a/docs-xml/wscript_build b/docs-xml/wscript_build
index 2b3a180..35d784a 100644
--- a/docs-xml/wscript_build
+++ b/docs-xml/wscript_build
@@ -51,7 +51,7 @@ manpages='''
manpages/vfs_aio_linux.8
manpages/vfs_aio_pthread.8
manpages/vfs_audit.8
- manpages/vfs_btrfs.8
+ manpages/vfs_btrfs.8
manpages/vfs_cacheprime.8
manpages/vfs_cap.8
manpages/vfs_catia.8
@@ -78,14 +78,17 @@ manpages='''
manpages/vfs_recycle.8
manpages/vfs_shadow_copy.8
manpages/vfs_shadow_copy2.8
- manpages/vfs_shell_snap.8
- manpages/vfs_snapper.8
+ manpages/vfs_shell_snap.8
+ manpages/vfs_snapper.8
manpages/vfs_streams_depot.8
manpages/vfs_streams_xattr.8
manpages/vfs_syncops.8
manpages/vfs_time_audit.8
manpages/vfs_tsmsm.8
manpages/vfs_unityed_media.8
+ manpages/vfs_virusfilter_clamav.8
+ manpages/vfs_virusfilter_fsav.8
+ manpages/vfs_virusfilter_sophos.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 0000000..41ce38a
--- /dev/null
+++ b/examples/scripts/vfs/virusfilter/virusfilter-notify.ksh
@@ -0,0 +1,281 @@
+#!/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 .
+##
+
+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 &
+ 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
+Infected file path: $VIRUSFILTER_SERVICE_PATH/$VIRUSFILTER_INFECTED_SERVICE_FILE_PATH
+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 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/lib/util/memcache.c b/lib/util/memcache.c
index 9e9a208..598dd00 100644
--- a/lib/util/memcache.c
+++ b/lib/util/memcache.c
@@ -54,6 +54,7 @@ static bool memcache_is_talloc(enum memcache_number n)
case PDB_GETPWSID_CACHE:
case SINGLETON_CACHE_TALLOC:
case SHARE_MODE_LOCK_CACHE:
+ case VIRUSFILTER_SCAN_RESULTS_CACHE_TALLOC:
result = true;
break;
default:
diff --git a/lib/util/memcache.h b/lib/util/memcache.h
index 2602fb7..c6203fb 100644
--- a/lib/util/memcache.h
+++ b/lib/util/memcache.h
@@ -41,7 +41,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 */
};
/*
diff --git a/source3/modules/vfs_virusfilter_clamav.c b/source3/modules/vfs_virusfilter_clamav.c
new file mode 100644
index 0000000..872c7c1
--- /dev/null
+++ b/source3/modules/vfs_virusfilter_clamav.c
@@ -0,0 +1,168 @@
+/*
+ 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 .
+*/
+
+#define VIRUSFILTER_ENGINE clamav
+#define VIRUSFILTER_MODULE_ENGINE "clamav"
+
+/* 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
+#define VIRUSFILTER_DEFAULT_CONNECT_TIMEOUT 30000 /* msec */
+#define VIRUSFILTER_DEFAULT_TIMEOUT 60000 /* msec */
+/* Default values for module-specific configuration variables */
+/* None */
+
+#define virusfilter_module_connect virusfilter_clamav_connect
+#define virusfilter_module_scan_init virusfilter_clamav_scan_init
+#define virusfilter_module_scan_end virusfilter_clamav_scan_end
+#define virusfilter_module_scan virusfilter_clamav_scan
+
+#include "modules/vfs_virusfilter_vfs.c"
+
+/* ====================================================================== */
+
+#include "modules/vfs_virusfilter_utils.h"
+
+/* ====================================================================== */
+
+static int virusfilter_clamav_connect(
+ vfs_handle_struct *vfs_h,
+ virusfilter_handle *virusfilter_h,
+ const char *svc,
+ const char *user)
+{
+ /* To use clamd "zXXXX" commands */
+ virusfilter_io_set_writel_eol(virusfilter_h->io_h, "\0", 1);
+ virusfilter_io_set_readl_eol(virusfilter_h->io_h, "\0", 1);
+
+ return 0;
+}
+
+static virusfilter_result virusfilter_clamav_scan_init(
+ virusfilter_handle *virusfilter_h)
+{
+ virusfilter_io_handle *io_h = virusfilter_h->io_h;
+ virusfilter_result result;
+
+ DEBUG(7,("clamd: Connecting to socket: %s\n",
+ virusfilter_h->socket_path));
+
+ become_root();
+ result = virusfilter_io_connect_path(io_h, virusfilter_h->socket_path);
+ unbecome_root();
+
+ if (result != VIRUSFILTER_RESULT_OK) {
+ DEBUG(0,("clamd: Connecting to socket failed: %s: %s\n",
+ virusfilter_h->socket_path, strerror(errno)));
+ return VIRUSFILTER_RESULT_ERROR;
+ }
+
+ DEBUG(7,("clamd: Connected\n"));
+
+ return VIRUSFILTER_RESULT_OK;
+}
+
+static void virusfilter_clamav_scan_end(virusfilter_handle *virusfilter_h)
+{
+ virusfilter_io_handle *io_h = virusfilter_h->io_h;
+
+ DEBUG(7,("clamd: Disconnecting\n"));
+
+ virusfilter_io_disconnect(io_h);
+}
+
+static virusfilter_result virusfilter_clamav_scan(
+ vfs_handle_struct *vfs_h,
+ virusfilter_handle *virusfilter_h,
+ const struct smb_filename *smb_fname,
+ const char **reportp)
+{
+ const char *connectpath = vfs_h->conn->connectpath;
+ const char *fname = smb_fname->base_name;
+ size_t filepath_len = strlen(connectpath) + 1 /* slash */ + strlen(fname);
+ virusfilter_io_handle *io_h = virusfilter_h->io_h;
+ virusfilter_result result = VIRUSFILTER_RESULT_CLEAN;
+ const char *report = NULL;
+ char *reply;
+ char *reply_token;
+
+ DEBUG(7,("Scanning file: %s/%s\n", connectpath, fname));
+
+ if (virusfilter_io_writefl_readl(io_h, "zSCAN %s/%s",
+ connectpath, fname) != VIRUSFILTER_RESULT_OK) {
+ DEBUG(0,("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 (io_h->r_buffer[filepath_len] != ':' ||
+ io_h->r_buffer[filepath_len+1] != ' ')
+ {
+ DEBUG(0,("clamd: zSCAN: Invalid reply: %s\n", io_h->r_buffer));
+ result = VIRUSFILTER_RESULT_ERROR;
+ report = "Scanner communication error";
+ goto virusfilter_clamav_scan_return;
+ }
+ reply = io_h->r_buffer + filepath_len + 2;
+
+ reply_token = strrchr(io_h->r_buffer, ' ');
+ if (!reply_token) {
+ DEBUG(0,("clamd: zSCAN: Invalid reply: %s\n", io_h->r_buffer));
+ result = VIRUSFILTER_RESULT_ERROR;
+ report = "Scanner communication error";
+ goto virusfilter_clamav_scan_return;
+ }
+ *reply_token = '\0';
+ reply_token++;
+
+ if (str_eq(reply_token, "OK") ) {
+ /* : OK */
+ result = VIRUSFILTER_RESULT_CLEAN;
+ report = "Clean";
+ } else if (str_eq(reply_token, "FOUND")) {
+ /* : FOUND */
+ result = VIRUSFILTER_RESULT_INFECTED;
+ report = talloc_strdup(talloc_tos(), reply);
+ } else if (str_eq(reply_token, "ERROR")) {
+ /* : ERROR */
+ DEBUG(0,("clamd: zSCAN: Error: %s\n", reply));
+ result = VIRUSFILTER_RESULT_ERROR;
+ report = talloc_asprintf(talloc_tos(),
+ "Scanner error: %s\t", reply);
+ } else {
+ DEBUG(0,("clamd: zSCAN: Invalid reply: %s\n", reply_token));
+ result = VIRUSFILTER_RESULT_ERROR;
+ report = "Scanner communication error";
+ }
+
+virusfilter_clamav_scan_return:
+ if (report == NULL) {
+ *reportp = "Scanner report memory error";
+ } else {
+ *reportp = report;
+ }
+
+ return result;
+}
+
diff --git a/source3/modules/vfs_virusfilter_common.h b/source3/modules/vfs_virusfilter_common.h
new file mode 100644
index 0000000..a04e85a
--- /dev/null
+++ b/source3/modules/vfs_virusfilter_common.h
@@ -0,0 +1,95 @@
+/*
+ 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 .
+*/
+
+#ifndef _VIRUSFILTER_COMMON_H
+#define _VIRUSFILTER_COMMON_H
+
+#include
+#include
+/* 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"
+
+#if (SMB_VFS_INTERFACE_VERSION < 28)
+#error "Samba 3.6+ required (SMB_VFS_INTERFACE_VERSION >= 28)"
+#endif
+
+/* Undefine Samba's PACKAGE_* macros */
+#undef PACKAGE_BUGREPORT
+#undef PACKAGE_NAME
+#undef PACKAGE_STRING
+#undef PACKAGE_TARNAME
+#undef PACKAGE_URL
+#undef PACKAGE_VERSION
+
+/* Samba debug classs for VIRUSFILTER */
+#undef DBGC_CLASS
+#define DBGC_CLASS virusfilter_debug_level
+extern int virusfilter_debug_level;
+
+/* Samba's global variable */
+extern userdom_struct current_user_info;
+
+#include "include/vfs_virusfilter_config.h"
+
+#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,
+ /* FIXME: VIRUSFILTER_RESULT_SUSPECTED, */
+ /* FIXME: VIRUSFILTER_RESULT_RISKWARE, */
+} virusfilter_result;
+
+#define conn_session_info(conn) ((conn)->session_info)
+#if SAMBA_VERSION_NUMBER >= 40200
+# define conn_socket(conn) ((conn)->transport.sock)
+#else
+# define conn_socket(conn) ((conn)->sconn->sock)
+#endif
+#define conn_domain_name(conn) ((conn)->session_info->info->domain_name)
+#define conn_client_name(conn) ((conn)->sconn->remote_hostname)
+#define conn_client_addr(conn) \
+ tsocket_address_inet_addr_string((conn)->sconn->remote_address, \
+ talloc_tos())
+
+#define conn_server_addr(conn) \
+ tsocket_address_inet_addr_string((conn)->sconn->local_address, \
+ talloc_tos())
+
+#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 0000000..5bc01ce
--- /dev/null
+++ b/source3/modules/vfs_virusfilter_fsav.c
@@ -0,0 +1,375 @@
+/*
+ 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 .
+*/
+
+#define VIRUSFILTER_ENGINE fsav
+#define VIRUSFILTER_MODULE_ENGINE "fsav"
+
+/* Default values for standard "extra" configuration variables */
+#define VIRUSFILTER_DEFAULT_SOCKET_PATH "/tmp/.fsav-0"
+#define VIRUSFILTER_DEFAULT_CONNECT_TIMEOUT 30000 /* msec */
+#define VIRUSFILTER_DEFAULT_TIMEOUT 60000 /* msec */
+#define VIRUSFILTER_DEFAULT_SCAN_ARCHIVE false
+#define VIRUSFILTER_DEFAULT_MAX_NESTED_SCAN_ARCHIVE 1
+#define VIRUSFILTER_DEFAULT_SCAN_REQUEST_LIMIT 0
+#define VIRUSFILTER_DEFAULT_SCAN_MIME false
+/* 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
+
+#define VIRUSFILTER_MODULE_CONFIG_MEMBERS \
+ int fsav_protocol; \
+ bool scan_riskware; \
+ bool stop_scan_on_first; \
+ bool filter_filename; \
+ /* End of VIRUSFILTER_MODULE_CONFIG_MEMBERS */
+
+#define virusfilter_module_connect virusfilter_fsav_connect
+#define virusfilter_module_destruct_config \
+ virusfilter_fsav_destruct_config
+#define virusfilter_module_scan_init virusfilter_fsav_scan_init
+#define virusfilter_module_scan_end virusfilter_fsav_scan_end
+#define virusfilter_module_scan virusfilter_fsav_scan
+
+#include "vfs_virusfilter_vfs.c"
+
+/* ====================================================================== */
+
+#include "vfs_virusfilter_utils.h"
+
+/* ====================================================================== */
+
+static int virusfilter_fsav_connect(
+ vfs_handle_struct *vfs_h,
+ virusfilter_handle *virusfilter_h,
+ const char *svc,
+ const char *user)
+{
+ int snum = SNUM(vfs_h->conn);
+
+ virusfilter_h->fsav_protocol = lp_parm_int(snum,
+ VIRUSFILTER_MODULE_NAME, "fsav protocol",
+ VIRUSFILTER_DEFAULT_FSAV_PROTOCOL);
+
+ virusfilter_h->scan_riskware = lp_parm_bool(snum,
+ VIRUSFILTER_MODULE_NAME, "scan riskware",
+ VIRUSFILTER_DEFAULT_SCAN_RISKWARE);
+
+ virusfilter_h->stop_scan_on_first = lp_parm_bool(snum,
+ VIRUSFILTER_MODULE_NAME, "stop scan on first",
+ VIRUSFILTER_DEFAULT_STOP_SCAN_ON_FIRST);
+
+ virusfilter_h->filter_filename = lp_parm_bool(snum,
+ VIRUSFILTER_MODULE_NAME, "filter filename",
+ VIRUSFILTER_DEFAULT_FILTER_FILENAME);
+
+ return 0;
+}
+
+static int virusfilter_fsav_destruct_config(
+ virusfilter_handle *virusfilter_h)
+{
+ virusfilter_fsav_scan_end(virusfilter_h);
+
+ return 0;
+}
+
+static virusfilter_result virusfilter_fsav_scan_init(
+ virusfilter_handle *virusfilter_h)
+{
+ virusfilter_io_handle *io_h = virusfilter_h->io_h;
+ virusfilter_result result;
+
+ if (io_h->socket != -1) {
+ DEBUG(10,("fsavd: Checking if connection is alive\n"));
+
+ /* FIXME: I don't know the correct PING command format... */
+ if (virusfilter_io_writefl_readl(io_h, "PING") ==
+ VIRUSFILTER_RESULT_OK)
+ {
+ if (strn_eq(io_h->r_buffer, "ERROR\t", 6)) {
+ DEBUG(10,("fsavd: Re-using existent "
+ "connection\n"));
+ return VIRUSFILTER_RESULT_OK;
+ }
+ }
+
+ DEBUG(10,("fsavd: Closing dead connection\n"));
+ virusfilter_fsav_scan_end(virusfilter_h);
+ }
+
+ DEBUG(7,("fsavd: Connecting to socket: %s\n",
+ virusfilter_h->socket_path));
+
+ become_root();
+ result = virusfilter_io_connect_path(io_h, virusfilter_h->socket_path);
+ unbecome_root();
+
+ if (result != VIRUSFILTER_RESULT_OK) {
+ DEBUG(0,("fsavd: Connecting to socket failed: %s: %s\n",
+ virusfilter_h->socket_path, strerror(errno)));
+ return VIRUSFILTER_RESULT_ERROR;
+ }
+
+ if (virusfilter_io_readl(io_h) != VIRUSFILTER_RESULT_OK) {
+ DEBUG(0,("fsavd: Reading greeting message failed: %s\n",
+ strerror(errno)));
+ goto virusfilter_fsav_init_failed;
+ }
+ if (!strn_eq(io_h->r_buffer, "DBVERSION\t", 10)) {
+ DEBUG(0,("fsavd: Invalid greeting message: %s\n",
+ io_h->r_buffer));
+ goto virusfilter_fsav_init_failed;
+ }
+
+ DEBUG(10,("fsavd: Connected\n"));
+
+ DEBUG(7,("fsavd: Configuring\n"));
+
+ if (virusfilter_io_writefl_readl(io_h,
+ "PROTOCOL\t%d", virusfilter_h->fsav_protocol)
+ != VIRUSFILTER_RESULT_OK) {
+ DEBUG(0,("fsavd: PROTOCOL: I/O error: %s\n", strerror(errno)));
+ goto virusfilter_fsav_init_failed;
+ }
+ if (!strn_eq(io_h->r_buffer, "OK\t", 3)) {
+ DEBUG(0,("fsavd: PROTOCOL: Not accepted: %s\n",
+ io_h->r_buffer));
+ goto virusfilter_fsav_init_failed;
+ }
+
+#if 0 /* FIXME */
+ if (virusfilter_io_writefl_readl(io_h,
+ "CONFIGURE\tTIMEOUT\t%d", virusfilter_h->timeout / 1000)
+ != VIRUSFILTER_RESULT_OK) {
+ DEBUG(0,("fsavd: CONFIGURE TIMEOUT: I/O error: %s\n",
+ strerror(errno)));
+ goto virusfilter_fsav_init_failed;
+ }
+ if (!strn_eq(io_h->r_buffer, "OK\t", 3)) {
+ DEBUG(0,("fsavd: CONFIGURE TIMEOUT: Not accepted: %s\n",
+ io_h->r_buffer));
+ goto virusfilter_fsav_init_failed;
+ }
+#endif
+
+ if (virusfilter_io_writefl_readl(io_h,
+ "CONFIGURE\tSTOPONFIRST\t%d", virusfilter_h->stop_scan_on_first ?
+ 1 : 0) != VIRUSFILTER_RESULT_OK)
+ {
+ DEBUG(0,("fsavd: CONFIGURE STOPONFIRST: I/O error: %s\n",
+ strerror(errno)));
+ goto virusfilter_fsav_init_failed;
+ }
+ if (!strn_eq(io_h->r_buffer, "OK\t", 3)) {
+ DEBUG(0,("fsavd: CONFIGURE STOPONFIRST: Not accepted: %s\n",
+ io_h->r_buffer));
+ goto virusfilter_fsav_init_failed;
+ }
+
+ if (virusfilter_io_writefl_readl(io_h,
+ "CONFIGURE\tFILTER\t%d", virusfilter_h->filter_filename ? 1 : 0)
+ != VIRUSFILTER_RESULT_OK) {
+ DEBUG(0,("fsavd: CONFIGURE FILTER: I/O error: %s\n",
+ strerror(errno)));
+ goto virusfilter_fsav_init_failed;
+ }
+ if (!strn_eq(io_h->r_buffer, "OK\t", 3)) {
+ DEBUG(0,("fsavd: CONFIGURE FILTER: Not accepted: %s\n",
+ io_h->r_buffer));
+ goto virusfilter_fsav_init_failed;
+ }
+
+ if (virusfilter_io_writefl_readl(io_h,
+ "CONFIGURE\tARCHIVE\t%d", virusfilter_h->scan_archive ? 1 : 0)
+ != VIRUSFILTER_RESULT_OK) {
+ DEBUG(0,("fsavd: CONFIGURE ARCHIVE: I/O error: %s\n",
+ strerror(errno)));
+ goto virusfilter_fsav_init_failed;
+ }
+ if (!strn_eq(io_h->r_buffer, "OK\t", 3)) {
+ DEBUG(0,("fsavd: CONFIGURE ARCHIVE: Not accepted: %s\n",
+ io_h->r_buffer));
+ goto virusfilter_fsav_init_failed;
+ }
+
+ if (virusfilter_io_writefl_readl(io_h,
+ "CONFIGURE\tMAXARCH\t%d", virusfilter_h->max_nested_scan_archive)
+ != VIRUSFILTER_RESULT_OK) {
+ DEBUG(0,("fsavd: CONFIGURE MAXARCH: I/O error: %s\n",
+ strerror(errno)));
+ goto virusfilter_fsav_init_failed;
+ }
+ if (!strn_eq(io_h->r_buffer, "OK\t", 3)) {
+ DEBUG(0,("fsavd: CONFIGURE MAXARCH: Not accepted: %s\n",
+ io_h->r_buffer));
+ goto virusfilter_fsav_init_failed;
+ }
+
+ if (virusfilter_io_writefl_readl(io_h,
+ "CONFIGURE\tMIME\t%d", virusfilter_h->scan_mime ? 1 : 0)
+ != VIRUSFILTER_RESULT_OK) {
+ DEBUG(0,("fsavd: CONFIGURE MIME: I/O error: %s\n",
+ strerror(errno)));
+ goto virusfilter_fsav_init_failed;
+ }
+ if (!strn_eq(io_h->r_buffer, "OK\t", 3)) {
+ DEBUG(0,("fsavd: CONFIGURE MIME: Not accepted: %s\n",
+ io_h->r_buffer));
+ goto virusfilter_fsav_init_failed;
+ }
+
+ if (virusfilter_io_writefl_readl(io_h,
+ "CONFIGURE\tRISKWARE\t%d", virusfilter_h->scan_riskware ? 1 : 0)
+ != VIRUSFILTER_RESULT_OK) {
+ DEBUG(0,("fsavd: CONFIGURE RISKWARE: I/O error: %s\n",
+ strerror(errno)));
+ goto virusfilter_fsav_init_failed;
+ }
+ if (!strn_eq(io_h->r_buffer, "OK\t", 3)) {
+ DEBUG(0,("fsavd: CONFIGURE RISKWARE: Not accepted: %s\n",
+ io_h->r_buffer));
+ goto virusfilter_fsav_init_failed;
+ }
+
+ DEBUG(10,("fsavd: Configured\n"));
+
+ return VIRUSFILTER_RESULT_OK;
+
+virusfilter_fsav_init_failed:
+ virusfilter_fsav_scan_end(virusfilter_h);
+
+ return VIRUSFILTER_RESULT_ERROR;
+}
+
+static void virusfilter_fsav_scan_end(virusfilter_handle *virusfilter_h)
+{
+ virusfilter_io_handle *io_h = virusfilter_h->io_h;
+
+ DEBUG(7,("fsavd: Disconnecting\n"));
+ virusfilter_io_disconnect(io_h);
+}
+
+static virusfilter_result virusfilter_fsav_scan(
+ vfs_handle_struct *vfs_h,
+ virusfilter_handle *virusfilter_h,
+ const struct smb_filename *smb_fname,
+ const char **reportp)
+{
+ const char *connectpath = vfs_h->conn->connectpath;
+ const char *fname = smb_fname->base_name;
+ virusfilter_io_handle *io_h = virusfilter_h->io_h;
+ virusfilter_result result = VIRUSFILTER_RESULT_CLEAN;
+ const char *report = NULL;
+ char *reply_token, *reply_saveptr;
+
+ DEBUG(7,("Scanning file: %s/%s\n", connectpath, fname));
+
+ if (virusfilter_io_writevl(io_h,
+ "SCAN\t", 5,
+ connectpath, (int)strlen(connectpath),
+ "/", 1,
+ fname, (int)strlen(fname),
+ NULL) != VIRUSFILTER_RESULT_OK) {
+ DEBUG(0,("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;
+ }
+
+ for (;;) {
+ if (virusfilter_io_readl(io_h) != VIRUSFILTER_RESULT_OK) {
+ DEBUG(0,("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(io_h->r_buffer, "\t", &reply_saveptr);
+
+ if (str_eq(reply_token, "OK") ) {
+ break;
+ } else if (str_eq(reply_token, "CLEAN") ) {
+ /* CLEAN\t */
+ result = VIRUSFILTER_RESULT_CLEAN;
+ report = "Clean";
+ } else if (str_eq(reply_token, "INFECTED") ||
+ str_eq(reply_token, "ARCHIVE_INFECTED") ||
+ str_eq(reply_token, "MIME_INFECTED") ||
+ str_eq(reply_token, "RISKWARE") ||
+ str_eq(reply_token, "ARCHIVE_RISKWARE") ||
+ str_eq(reply_token, "MIME_RISKWARE")) {
+ /* INFECTED\t\t\t */
+ result = VIRUSFILTER_RESULT_INFECTED;
+ reply_token = strtok_r(NULL, "\t", &reply_saveptr);
+ reply_token = strtok_r(NULL, "\t", &reply_saveptr);
+ if (reply_token) {
+ report = talloc_strdup(talloc_tos(),
+ reply_token);
+ } else {
+ report = "UNKNOWN INFECTION";
+ }
+ } else if (str_eq(reply_token, "OPEN_ARCHIVE")) {
+ /* Ignore */
+ } else if (str_eq(reply_token, "CLOSE_ARCHIVE")) {
+ /* Ignore */
+ } else if (str_eq(reply_token, "SUSPECTED") ||
+ str_eq(reply_token, "ARCHIVE_SUSPECTED") ||
+ str_eq(reply_token, "MIME_SUSPECTED")) {
+#if 0
+ // FIXME: Block if "block suspected file" option is
+ // true
+ result = VIRUSFILTER_RESULT_SUSPECTED;
+ ...
+#else
+ /* Ignore */
+#endif
+ } else if (str_eq(reply_token, "SCAN_FAILURE")) {
+ /* SCAN_FAILURE\t\t0x\t [] */
+ result = VIRUSFILTER_RESULT_ERROR;
+ reply_token = strtok_r(NULL, "\t", &reply_saveptr);
+ reply_token = strtok_r(NULL, "\t", &reply_saveptr);
+ DEBUG(0,("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;
+ DEBUG(0,("fsavd: SCANFILE: Invalid reply: %s\t",
+ reply_token));
+ report = "Scanner communication error";
+ }
+ }
+
+virusfilter_fsav_scan_return:
+ if (report == NULL) {
+ *reportp = "Scanner report memory error";
+ } else {
+ *reportp = report;
+ }
+
+ return result;
+}
+
diff --git a/source3/modules/vfs_virusfilter_sophos.c b/source3/modules/vfs_virusfilter_sophos.c
new file mode 100644
index 0000000..31c5db4
--- /dev/null
+++ b/source3/modules/vfs_virusfilter_sophos.c
@@ -0,0 +1,309 @@
+/*
+ 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 .
+*/
+
+#define VIRUSFILTER_ENGINE sophos
+#define VIRUSFILTER_MODULE_ENGINE "sophos"
+
+/* 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
+#define VIRUSFILTER_DEFAULT_CONNECT_TIMEOUT 30000 /* msec */
+#define VIRUSFILTER_DEFAULT_TIMEOUT 60000 /* msec */
+#define VIRUSFILTER_DEFAULT_SCAN_REQUEST_LIMIT 0
+#define VIRUSFILTER_DEFAULT_SCAN_ARCHIVE false
+/* Default values for module-specific configuration variables */
+/* None */
+
+#define virusfilter_module_connect virusfilter_sophos_connect
+#define virusfilter_module_scan_init virusfilter_sophos_scan_init
+#define virusfilter_module_scan_end virusfilter_sophos_scan_end
+#define virusfilter_module_scan virusfilter_sophos_scan
+
+#include "modules/vfs_virusfilter_vfs.c"
+
+/* ====================================================================== */
+
+#include "modules/vfs_virusfilter_utils.h"
+
+/* ====================================================================== */
+
+static int virusfilter_sophos_connect(
+ vfs_handle_struct *vfs_h,
+ virusfilter_handle *virusfilter_h,
+ const char *svc,
+ const char *user)
+{
+ virusfilter_io_set_readl_eol(virusfilter_h->io_h, "\x0D\x0A", 2);
+
+ return 0;
+}
+
+static virusfilter_result virusfilter_sophos_scan_ping(
+ virusfilter_handle *virusfilter_h)
+{
+ virusfilter_io_handle *io_h = virusfilter_h->io_h;
+
+ /* SSSP/1.0 has no "PING" command */
+ if (virusfilter_io_writel(io_h, "SSSP/1.0 OPTIONS\n", 17) !=
+ VIRUSFILTER_RESULT_OK)
+ {
+ return VIRUSFILTER_RESULT_ERROR;
+ }
+
+ for (;;) {
+ if (virusfilter_io_readl(io_h) != VIRUSFILTER_RESULT_OK) {
+ return VIRUSFILTER_RESULT_ERROR;
+ }
+ if (str_eq(io_h->r_buffer, "")) {
+ break;
+ }
+ }
+
+ return VIRUSFILTER_RESULT_OK;
+}
+
+static virusfilter_result virusfilter_sophos_scan_init(
+ virusfilter_handle *virusfilter_h)
+{
+ virusfilter_io_handle *io_h = virusfilter_h->io_h;
+ virusfilter_result result;
+
+ if (io_h->socket != -1) {
+ DEBUG(10,("SSSP: Checking if connection is alive\n"));
+
+ if (virusfilter_sophos_scan_ping(virusfilter_h) ==
+ VIRUSFILTER_RESULT_OK)
+ {
+ DEBUG(10,("SSSP: Re-using existent connection\n"));
+ return VIRUSFILTER_RESULT_OK;
+ }
+
+ DEBUG(7,("SSSP: Closing dead connection\n"));
+ virusfilter_sophos_scan_end(virusfilter_h);
+ }
+
+
+ DEBUG(7,("SSSP: Connecting to socket: %s\n",
+ virusfilter_h->socket_path));
+
+ become_root();
+ result = virusfilter_io_connect_path(io_h, virusfilter_h->socket_path);
+ unbecome_root();
+
+ if (result != VIRUSFILTER_RESULT_OK) {
+ DEBUG(0,("SSSP: Connecting to socket failed: %s: %s\n",
+ virusfilter_h->socket_path, strerror(errno)));
+ return VIRUSFILTER_RESULT_ERROR;
+ }
+
+ if (virusfilter_io_readl(io_h) != VIRUSFILTER_RESULT_OK) {
+ DEBUG(0,("SSSP: Reading greeting message failed: %s\n",
+ strerror(errno)));
+ goto virusfilter_sophos_scan_init_failed;
+ }
+ if (!strn_eq(io_h->r_buffer, "OK SSSP/1.0", 11)) {
+ DEBUG(0,("SSSP: Invalid greeting message: %s\n",
+ io_h->r_buffer));
+ goto virusfilter_sophos_scan_init_failed;
+ }
+
+ DEBUG(10,("SSSP: Connected\n"));
+
+ DEBUG(7,("SSSP: Configuring\n"));
+
+ if (virusfilter_io_writefl_readl(io_h,
+ "SSSP/1.0 OPTIONS\n"
+ "output:brief\n"
+ "savigrp:GrpArchiveUnpack %d\n",
+ virusfilter_h->scan_archive ? 1 : 0)
+ != VIRUSFILTER_RESULT_OK) {
+ DEBUG(0,("SSSP: OPTIONS: I/O error: %s\n", strerror(errno)));
+ goto virusfilter_sophos_scan_init_failed;
+ }
+ if (!strn_eq(io_h->r_buffer, "ACC ", 4)) {
+ DEBUG(0,("SSSP: OPTIONS: Not accepted: %s\n", io_h->r_buffer));
+ goto virusfilter_sophos_scan_init_failed;
+ }
+ if (virusfilter_io_readl(io_h) != VIRUSFILTER_RESULT_OK) {
+ DEBUG(0,("SSSP: OPTIONS: Read error: %s\n", strerror(errno)));
+ goto virusfilter_sophos_scan_init_failed;
+ }
+ if (!strn_eq(io_h->r_buffer, "DONE OK ", 8)) {
+ DEBUG(0,("SSSP: OPTIONS failed: %s\n", io_h->r_buffer));
+ goto virusfilter_sophos_scan_init_failed;
+ }
+ if (virusfilter_io_readl(io_h) != VIRUSFILTER_RESULT_OK) {
+ DEBUG(0,("SSSP: OPTIONS: Read error: %s\n", strerror(errno)));
+ goto virusfilter_sophos_scan_init_failed;
+ }
+ if (!str_eq(io_h->r_buffer, "")) {
+ DEBUG(0,("SSSP: OPTIONS: Invalid reply: %s\n", io_h->r_buffer));
+ goto virusfilter_sophos_scan_init_failed;
+ }
+
+ DEBUG(10,("SSSP: Configured\n"));
+
+ return VIRUSFILTER_RESULT_OK;
+
+virusfilter_sophos_scan_init_failed:
+
+ virusfilter_sophos_scan_end(virusfilter_h);
+
+ return VIRUSFILTER_RESULT_ERROR;
+}
+
+static void virusfilter_sophos_scan_end(virusfilter_handle *virusfilter_h)
+{
+ virusfilter_io_handle *io_h = virusfilter_h->io_h;
+
+ DEBUG(7,("SSSP: Disconnecting\n"));
+
+ virusfilter_io_disconnect(io_h);
+}
+
+static virusfilter_result virusfilter_sophos_scan(
+ vfs_handle_struct *vfs_h,
+ virusfilter_handle *virusfilter_h,
+ const struct smb_filename *smb_fname,
+ const char **reportp)
+{
+ const char *connectpath = vfs_h->conn->connectpath;
+ const char *fname = smb_fname->base_name;
+ char fileurl[VIRUSFILTER_IO_URL_MAX+1];
+ int fileurl_len, fileurl_len2;
+ virusfilter_io_handle *io_h = virusfilter_h->io_h;
+ virusfilter_result result = VIRUSFILTER_RESULT_ERROR;
+ const char *report = NULL;
+ char *reply_token, *reply_saveptr;
+
+ DEBUG(7,("Scanning file: %s/%s\n", connectpath, fname));
+
+ fileurl_len = virusfilter_url_quote(connectpath, fileurl,
+ VIRUSFILTER_IO_URL_MAX);
+ if (fileurl_len < 0) {
+ DEBUG(0,("virusfilter_url_quote failed: File path too long: "
+ "%s/%s\n", connectpath, fname));
+ result = VIRUSFILTER_RESULT_ERROR;
+ report = "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) {
+ DEBUG(0,("virusfilter_url_quote failed: File path too long: "
+ "%s/%s\n", connectpath, fname));
+ result = VIRUSFILTER_RESULT_ERROR;
+ report = "File path too long";
+ goto virusfilter_sophos_scan_return;
+ }
+ fileurl_len += fileurl_len2;
+
+ if (virusfilter_io_writevl(io_h,
+ "SSSP/1.0 SCANFILE ", 18,
+ fileurl, fileurl_len,
+ NULL
+ ) != VIRUSFILTER_RESULT_OK) {
+ DEBUG(0,("SSSP: SCANFILE: Write error: %s\n",
+ strerror(errno)));
+ goto virusfilter_sophos_scan_io_error;
+ }
+
+ if (virusfilter_io_readl(io_h) != VIRUSFILTER_RESULT_OK) {
+ DEBUG(0,("SSSP: SCANFILE: Read error: %s\n", strerror(errno)));
+ goto virusfilter_sophos_scan_io_error;
+ }
+ if (!strn_eq(io_h->r_buffer, "ACC ", 4)) {
+ DEBUG(0,("SSSP: SCANFILE: Not accepted: %s\n",
+ io_h->r_buffer));
+ result = VIRUSFILTER_RESULT_ERROR;
+ goto virusfilter_sophos_scan_return;
+ }
+
+ result = VIRUSFILTER_RESULT_CLEAN;
+ for (;;) {
+ if (virusfilter_io_readl(io_h) != VIRUSFILTER_RESULT_OK) {
+ DEBUG(0,("SSSP: SCANFILE: Read error: %s\n",
+ strerror(errno)));
+ goto virusfilter_sophos_scan_io_error;
+ }
+
+ if (str_eq(io_h->r_buffer, "") ) {
+ break;
+ }
+
+ reply_token = strtok_r(io_h->r_buffer, " ", &reply_saveptr);
+
+ if (str_eq(reply_token, "VIRUS")) {
+ result = VIRUSFILTER_RESULT_INFECTED;
+ reply_token = strtok_r(NULL, " ", &reply_saveptr);
+ if (reply_token) {
+ report = talloc_strdup(talloc_tos(),
+ reply_token);
+ } else {
+ report = "UNKNOWN INFECTION";
+ }
+ } else if (str_eq(reply_token, "OK")) {
+ /* Ignore */
+ } else if (str_eq(reply_token, "DONE")) {
+ reply_token = strtok_r(NULL, "", &reply_saveptr);
+ if (reply_token &&
+ /* Succeed */
+ !strn_eq(reply_token, "OK 0000 ", 8) &&
+ /* Infected */
+ !strn_eq(reply_token, "OK 0203 ", 8))
+ {
+ DEBUG(0,("SSSP: SCANFILE: Error: %s\n",
+ reply_token));
+ result = VIRUSFILTER_RESULT_ERROR;
+ report = talloc_asprintf(talloc_tos(),
+ "Scanner error: %s\n", reply_token);
+ }
+ } else {
+ DEBUG(0,("SSSP: SCANFILE: Invalid reply: %s\n",
+ reply_token));
+ result = VIRUSFILTER_RESULT_ERROR;
+ report = "Scanner communication error";
+ }
+ }
+
+virusfilter_sophos_scan_return:
+ if (report == NULL) {
+ *reportp = "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));
+ if (reportp == NULL) {
+ *reportp = "Scanner I/O error and unable to talloc\n";
+ }
+
+ return result;
+}
+
diff --git a/source3/modules/vfs_virusfilter_utils.c b/source3/modules/vfs_virusfilter_utils.c
new file mode 100644
index 0000000..956066a
--- /dev/null
+++ b/source3/modules/vfs_virusfilter_utils.c
@@ -0,0 +1,893 @@
+/*
+ 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 .
+*/
+
+#include "lib/util/sys_rw_data.h"
+#include "modules/vfs_virusfilter_common.h"
+#include "modules/vfs_virusfilter_utils.h"
+
+#include
+
+int virusfilter_debug_level = DBGC_VFS;
+
+/* ====================================================================== */
+
+#ifndef HAVE_MEMMEM
+void *memmem(const void *m1, size_t m1_len, const void *m2, size_t m2_len)
+{
+ const char *m1_cur = (const char *)m1;
+ const char *m1_end = (const char *)m1 + m1_len - m2_len;
+ const char *m2_cur = (const char *)m2;
+
+ if (m1_len == 0 || m2_len == 0 || m1_len < m2_len) {
+ return NULL;
+ }
+
+ if (m2_len == 1) {
+ return memchr(m1_cur, *m2_cur, m1_len);
+ }
+
+ while (m1_cur <= m1_end) {
+ if (*m1_cur == *m2_cur) {
+ if (memcmp(m1_cur+1, m2_cur+1, m2_len-1) == 0) {
+ return (void *)m1_cur;
+ }
+ }
+ m1_cur++;
+ }
+
+ return NULL;
+}
+#endif
+
+/* ====================================================================== */
+
+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(conn)->unix_info->unix_name,
+ conn->connectpath,
+ conn_session_info(conn)->unix_token->gid,
+ conn_session_info(conn)->unix_info->sanitized_username,
+ conn_domain_name(conn),
+ str);
+}
+
+/* Python's urllib.quote(string[, safe]) clone */
+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);
+}
+
+#if SAMBA_VERSION_NUMBER >= 30600
+/*********************************************************
+ For rename across filesystems initial Patch from Warren Birnbaum
+
+**********************************************************/
+
+static int virusfilter_copy_reg(const char *source, const char *dest)
+{
+ SMB_STRUCT_STAT source_stats;
+ int saved_errno;
+ int ifd = -1;
+ int ofd = -1;
+
+ if (sys_lstat(source, &source_stats, false) == -1) {
+ return -1;
+ }
+
+ if (!S_ISREG(source_stats.st_ex_mode)) {
+ return -1;
+ }
+
+#if 0
+ if (source_stats.st_ex_size > module_sizelimit) {
+ DEBUG(5,
+ ("%s: size of %s larger than sizelimit (%lld > %lld), "
+ "rename prohititted\n", MODULE, source,
+ (long long)source_stats.st_ex_size,
+ (long long)module_sizelimit));
+ return -1;
+ }
+#endif
+
+ ifd = open(source, O_RDONLY, 0);
+ if (ifd < 0) {
+ return -1;
+ }
+
+ if (unlink(dest) && errno != ENOENT) {
+ return -1;
+ }
+
+#ifdef O_NOFOLLOW
+ ofd = open(dest, O_WRONLY | O_CREAT | O_TRUNC | O_NOFOLLOW, 0600);
+
+#else
+ ofd = open(dest, O_WRONLY | O_CREAT | O_TRUNC , 0600);
+#endif
+ if (ofd < 0 ) {
+ goto err;
+ }
+
+ if (transfer_file(ifd, ofd, (size_t)-1) == -1) {
+ goto err;
+ }
+
+ /*
+ * Try to preserve ownership. For non-root it might fail, but that's ok.
+ * But root probably wants to know, e.g. if NFS disallows it.
+ */
+
+#ifdef HAVE_FCHOWN
+ if ((fchown(ofd, source_stats.st_ex_uid, source_stats.st_ex_gid) == -1)
+ && (errno != EPERM)) {
+#else
+ if ((chown(dest, source_stats.st_ex_uid, source_stats.st_ex_gid) == -1)
+ && (errno != EPERM)) {
+#endif
+ goto err;
+ }
+
+ /*
+ * fchown turns off set[ug]id bits for non-root,
+ * so do the chmod last.
+ */
+
+#if defined(HAVE_FCHMOD)
+ if (fchmod(ofd, source_stats.st_ex_mode & 07777)) {
+#else
+ if (chmod(dest, source_stats.st_ex_mode & 07777)) {
+#endif
+ goto err;
+ }
+
+ if (close(ifd) == -1) {
+ goto err;
+ }
+
+ if (close(ofd) == -1) {
+ return -1;
+ }
+
+ /* Try to copy the old file's modtime and access time. */
+#if defined(HAVE_UTIMENSAT)
+ {
+ struct timespec ts[2];
+
+ ts[0] = source_stats.st_ex_atime;
+ ts[1] = source_stats.st_ex_mtime;
+ utimensat(AT_FDCWD, dest, ts, AT_SYMLINK_NOFOLLOW);
+ }
+#elif defined(HAVE_UTIMES)
+ {
+ struct timeval tv[2];
+
+ tv[0] = convert_timespec_to_timeval(source_stats.st_ex_atime);
+ tv[1] = convert_timespec_to_timeval(source_stats.st_ex_mtime);
+#ifdef HAVE_LUTIMES
+ lutimes(dest, tv);
+#else
+ utimes(dest, tv);
+#endif
+ }
+#elif defined(HAVE_UTIME)
+ {
+ struct utimbuf tv;
+
+ tv.actime = convert_timespec_to_time_t(
+ source_stats.st_ex_atime);
+ tv.modtime = convert_timespec_to_time_t(
+ source_stats.st_ex_mtime);
+ utime(dest, &tv);
+ }
+#endif
+
+ if (unlink(source) == -1) {
+ return -1;
+ }
+
+ return 0;
+
+ err:
+
+ saved_errno = errno;
+ if (ifd != -1) {
+ close(ifd);
+ }
+ if (ofd != -1) {
+ close(ofd);
+ }
+ errno = saved_errno;
+ return -1;
+}
+#endif /* SAMBA_VERSION_NUMBER >= 30600 */
+
+#if SAMBA_VERSION_NUMBER >= 30600
+int virusfilter_vfs_next_move(
+ 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;
+ }
+
+ return virusfilter_copy_reg(smb_fname_src->base_name,
+ smb_fname_dst->base_name);
+}
+#endif /* SAMBA_VERSION_NUMBER >= 30600 */
+
+/* Line-based socket I/O
+ * ====================================================================== */
+
+virusfilter_io_handle *virusfilter_io_new(
+ TALLOC_CTX *mem_ctx,
+ int connect_timeout,
+ int io_timeout)
+{
+ virusfilter_io_handle *io_h = talloc_zero(mem_ctx,
+ virusfilter_io_handle);
+
+ if (!io_h) {
+ return NULL;
+ }
+
+ io_h->socket = -1;
+ 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(
+ 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(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(
+ 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(
+ 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;
+}
+
+virusfilter_result virusfilter_io_connect_path(
+ virusfilter_io_handle *io_h,
+ const char *path)
+{
+ struct sockaddr_un addr;
+ NTSTATUS status;
+
+ 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,
+ &io_h->socket);
+ if (!NT_STATUS_IS_OK(status)) {
+ io_h->socket = -1;
+ return VIRUSFILTER_RESULT_ERROR;
+ }
+
+ return VIRUSFILTER_RESULT_OK;
+}
+
+virusfilter_result virusfilter_io_disconnect(virusfilter_io_handle *io_h)
+{
+ if (io_h->socket != -1) {
+ close(io_h->socket);
+ io_h->socket = -1;
+ }
+
+ io_h->r_size = io_h->r_rest_size = 0;
+ io_h->r_rest_buffer = NULL;
+
+ return VIRUSFILTER_RESULT_OK;
+}
+
+virusfilter_result virusfilter_io_write(
+ virusfilter_io_handle *io_h,
+ const char *data,
+ size_t data_size)
+{
+ if (data_size == 0) {
+ return VIRUSFILTER_RESULT_OK;
+ }
+
+ switch (write_data_timeout(io_h->socket, data, data_size,
+ io_h->io_timeout, 1)) {
+ case -1:
+ return VIRUSFILTER_RESULT_ERROR;
+ default:
+ return VIRUSFILTER_RESULT_OK;
+ }
+}
+
+virusfilter_result virusfilter_io_writel(
+ virusfilter_io_handle *io_h,
+ const char *data,
+ size_t data_size)
+{
+ virusfilter_result result;
+
+ result = virusfilter_io_write(io_h, data, data_size);
+ if (result != VIRUSFILTER_RESULT_OK) {
+ return result;
+ }
+
+ return virusfilter_io_write(io_h, io_h->w_eol, io_h->w_eol_size);
+}
+
+virusfilter_result virusfilter_io_writefl(
+ 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);
+
+ 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);
+}
+
+virusfilter_result virusfilter_io_vwritefl(
+ 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);
+
+ 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);
+}
+
+virusfilter_result virusfilter_io_writev(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) {
+ break;
+ }
+ iov_p->iov_len = va_arg(ap, int);
+ }
+ va_end(ap);
+
+ switch (write_data_iov_timeout(io_h->socket, iov, iov_n,
+ io_h->io_timeout, 1))
+ {
+ case -1:
+ return VIRUSFILTER_RESULT_ERROR;
+ default:
+ return VIRUSFILTER_RESULT_OK;
+ }
+
+#if 0
+ /* Not reached */
+ return VIRUSFILTER_RESULT_OK;
+#endif
+}
+
+virusfilter_result virusfilter_io_writevl(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) {
+ 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++;
+
+ switch (write_data_iov_timeout(io_h->socket, iov, iov_n,
+ io_h->io_timeout, 1)) {
+ case -1:
+ return VIRUSFILTER_RESULT_ERROR;
+ default:
+ return VIRUSFILTER_RESULT_OK;
+ }
+
+#if 0
+ /* Not reached */
+ return VIRUSFILTER_RESULT_OK;
+#endif
+}
+
+virusfilter_result virusfilter_io_readl(virusfilter_io_handle *io_h)
+{
+ char *buffer;
+ ssize_t buffer_size = VIRUSFILTER_IO_BUFFER_SIZE;
+ struct pollfd pollfd;
+ ssize_t read_size;
+ char *eol;
+
+ if (io_h->r_rest_buffer == NULL) {
+ DEBUG(11,("Rest data not found in read buffer\n"));
+ buffer = io_h->r_buffer = io_h->r_buffer_real;
+ buffer_size = VIRUSFILTER_IO_BUFFER_SIZE;
+ } else {
+ DEBUG(11,("Rest data found in read buffer: %s, size=%ld\n",
+ io_h->r_rest_buffer, (long)io_h->r_rest_size));
+ eol = memmem(io_h->r_rest_buffer, io_h->r_rest_size,
+ io_h->r_eol, io_h->r_eol_size);
+ if (eol) {
+ *eol = '\0';
+ io_h->r_buffer = io_h->r_rest_buffer;
+ io_h->r_size = eol - io_h->r_rest_buffer;
+ DEBUG(11,("Read line data from read buffer: %s\n",
+ io_h->r_buffer));
+
+ io_h->r_rest_size -= io_h->r_size + io_h->r_eol_size;
+ io_h->r_rest_buffer = (io_h->r_rest_size > 0) ?
+ (eol + io_h->r_eol_size) : NULL;
+
+ return VIRUSFILTER_RESULT_OK;
+ }
+
+ io_h->r_buffer = io_h->r_buffer_real;
+ memmove(io_h->r_buffer, io_h->r_rest_buffer,
+ io_h->r_rest_size);
+
+ buffer = io_h->r_buffer + io_h->r_size;
+ buffer_size = VIRUSFILTER_IO_BUFFER_SIZE - io_h->r_rest_size;
+ }
+
+ io_h->r_rest_buffer = NULL;
+ io_h->r_rest_size = 0;
+
+ pollfd.fd = io_h->socket;
+ pollfd.events = POLLIN;
+
+ while (buffer_size > 0) {
+ switch (poll(&pollfd, 1, io_h->io_timeout)) {
+ case -1:
+ if (errno == EINTR) {
+ errno = 0;
+ continue;
+ }
+ return VIRUSFILTER_RESULT_ERROR;
+ case 0:
+ errno = ETIMEDOUT;
+ return VIRUSFILTER_RESULT_ERROR;
+ }
+
+ read_size = read(io_h->socket, buffer, buffer_size);
+ if (read_size == -1) {
+ if (errno == EINTR) {
+ errno = 0;
+ continue;
+ }
+ return VIRUSFILTER_RESULT_ERROR;
+ }
+
+ buffer[read_size] = '\0';
+
+ if (read_size == 0) { /* EOF */
+ return VIRUSFILTER_RESULT_OK;
+ }
+
+ io_h->r_size += read_size;
+
+ eol = memmem(io_h->r_buffer, read_size, io_h->r_eol,
+ io_h->r_eol_size);
+ if (eol) {
+ *eol = '\0';
+ DEBUG(11,("Read line data from socket: %s\n",
+ io_h->r_buffer));
+ io_h->r_size = eol - io_h->r_buffer;
+ io_h->r_rest_size = read_size - (eol - buffer +
+ io_h->r_eol_size);
+ if (io_h->r_rest_size > 0) {
+ io_h->r_rest_buffer = eol + io_h->r_eol_size;
+ DEBUG(11,("Rest data in read buffer: %s, "
+ "size=%ld\n", io_h->r_rest_buffer,
+ (long)io_h->r_rest_size));
+ }
+ return VIRUSFILTER_RESULT_OK;
+ }
+
+ buffer += read_size;
+ buffer_size -= read_size;
+ }
+
+ errno = E2BIG;
+
+ return VIRUSFILTER_RESULT_ERROR;
+}
+
+virusfilter_result virusfilter_io_writefl_readl(
+ virusfilter_io_handle *io_h,
+ const char *fmt, ...)
+{
+ virusfilter_result result;
+
+ if (fmt) {
+ va_list ap;
+
+ va_start(ap, fmt);
+ result = virusfilter_io_vwritefl(io_h, fmt, ap);
+ va_end(ap);
+
+ if (result != VIRUSFILTER_RESULT_OK) {
+ return result;
+ }
+ }
+
+ result = virusfilter_io_readl(io_h);
+ if (result != VIRUSFILTER_RESULT_OK) {
+ DEBUG(0,("virusfilter_io_readl not OK: %d\n", result));
+ return VIRUSFILTER_RESULT_ERROR;
+ }
+ if (io_h->r_size == 0) { /* EOF */
+ DEBUG(0,("virusfilter_io_readl EOF\n"));
+ return VIRUSFILTER_RESULT_ERROR;
+ }
+
+ return VIRUSFILTER_RESULT_OK;
+}
+
+/* Generic "stupid" cache
+ * ====================================================================== */
+
+virusfilter_cache_handle *virusfilter_cache_new(
+ TALLOC_CTX *ctx,
+ int entry_limit,
+ time_t time_limit)
+{
+ virusfilter_cache_handle *cache_h;
+
+ if (time_limit == 0) return NULL;
+
+ cache_h = talloc_zero(ctx, virusfilter_cache_handle);
+ if (!cache_h) {
+ DEBUG(0,("talloc_zero failed.\n"));
+ return NULL;
+ }
+
+ cache_h->cache = memcache_init(cache_h->ctx, entry_limit *
+ (sizeof(virusfilter_cache_entry) +
+ VIRUSFILTER_CACHE_BUFFER_SIZE));
+ if (!cache_h->cache) {
+ DEBUG(0,("memcache_init failed.\n"));
+ return NULL;
+ }
+ cache_h->ctx = ctx;
+ cache_h->time_limit = time_limit;
+
+ return cache_h;
+}
+
+int virusfilter_cache_entry_add(
+ virusfilter_cache_handle *cache_h,
+ const char *fname,
+ virusfilter_result result,
+ const char *report)
+{
+ int blob_size = sizeof(virusfilter_cache_entry);
+ virusfilter_cache_entry *cache_e = talloc_zero_size(NULL, blob_size);
+ int fname_len = strlen(fname);
+
+ if (!cache_e || cache_h->time_limit == 0) {
+ return 0;
+ }
+
+ cache_e->result = result;
+ if (report != NULL) {
+ cache_e->report = talloc_strdup(cache_e, report);
+ if (!cache_e->report) return 0;
+ }
+ if (cache_h->time_limit > 0) {
+ cache_e->time = time(NULL);
+ }
+
+ memcache_add_talloc(cache_h->cache,
+ VIRUSFILTER_SCAN_RESULTS_CACHE_TALLOC,
+ data_blob_const(fname, fname_len), &cache_e);
+
+ return 1;
+}
+
+int virusfilter_cache_entry_rename(
+ virusfilter_cache_handle *cache_h,
+ const char *old_fname,
+ const char *new_fname)
+{
+ int old_fname_len = strlen(old_fname);
+ int new_fname_len = strlen(new_fname);
+ virusfilter_cache_entry *new_data;
+
+ virusfilter_cache_entry *old_data = memcache_lookup_talloc(
+ cache_h->cache,
+ VIRUSFILTER_SCAN_RESULTS_CACHE_TALLOC,
+ data_blob_const(old_fname, old_fname_len));
+
+ if (old_data == NULL) {
+ return 0;
+ }
+
+ new_data = talloc_memdup(cache_h->ctx, old_data,
+ sizeof(virusfilter_cache_entry));
+ if (new_data == NULL) {
+ return 0;
+ }
+ new_data->report = talloc_strdup(new_data, old_data->report);
+
+ memcache_add_talloc(cache_h->cache,
+ VIRUSFILTER_SCAN_RESULTS_CACHE_TALLOC,
+ data_blob_const(new_fname, new_fname_len), &new_data);
+
+ memcache_delete(cache_h->cache, VIRUSFILTER_SCAN_RESULTS_CACHE_TALLOC,
+ data_blob_const(old_fname, old_fname_len));
+
+ return 1;
+}
+
+void virusfilter_cache_purge(virusfilter_cache_handle *cache_h)
+{
+ memcache_flush(cache_h->cache, VIRUSFILTER_SCAN_RESULTS_CACHE_TALLOC);
+}
+
+virusfilter_cache_entry *virusfilter_cache_get(
+ virusfilter_cache_handle *cache_h,
+ const char *fname)
+{
+ int fname_len = strlen(fname);
+ virusfilter_cache_entry *cache_e = NULL;
+ virusfilter_cache_entry *data = memcache_lookup_talloc(cache_h->cache,
+ VIRUSFILTER_SCAN_RESULTS_CACHE_TALLOC,
+ data_blob_const(fname, fname_len));
+
+ if (data != NULL) {
+ if (cache_h->time_limit > 0) {
+ if (time(NULL) - data->time > cache_h->time_limit)
+ {
+ DEBUG(10,("Cache entry is too old: %s\n",
+ fname));
+ virusfilter_cache_remove(cache_h, fname);
+ return cache_e;
+ }
+ }
+ cache_e = talloc_memdup(cache_h->ctx, data,
+ sizeof(virusfilter_cache_entry));
+ if (!cache_e) return cache_e;
+ if (data->report) {
+ cache_e->report = talloc_strdup(cache_e, data->report);
+ } else {
+ cache_e->report = NULL;
+ }
+ }
+
+ return cache_e;
+}
+
+void virusfilter_cache_remove(virusfilter_cache_handle *cache_h,
+ const char *fname)
+{
+ DEBUG(10,("Purging cache entry: %s\n", fname));
+
+ memcache_delete(cache_h->cache, VIRUSFILTER_SCAN_RESULTS_CACHE_TALLOC,
+ data_blob_const(fname, strlen(fname)));
+}
+
+void virusfilter_cache_entry_free(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) {
+ DEBUG(0,("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);
+ const char *server_addr_p;
+ char *client_addr_p;
+ const char *local_machine_name = get_local_machine_name();
+ fstring pidstr;
+
+ if (!local_machine_name || !*local_machine_name) {
+ local_machine_name = lp_netbios_name();
+ }
+
+ server_addr_p = conn_server_addr(conn);
+ if (strncmp("::ffff:", server_addr_p, 7) == 0) {
+ server_addr_p += 7;
+ }
+ virusfilter_env_set(mem_ctx, env_list, "VIRUSFILTER_SERVER_IP",
+ 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->connectpath);
+
+ client_addr_p = conn_client_addr(conn);
+ if (strncmp("::ffff:", client_addr_p, 7) == 0) {
+ client_addr_p += 7;
+ }
+ virusfilter_env_set(mem_ctx, env_list, "VIRUSFILTER_CLIENT_IP",
+ client_addr_p);
+ virusfilter_env_set(mem_ctx, env_list, "VIRUSFILTER_CLIENT_NAME",
+ conn_client_name(conn));
+ 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)
+{
+ if (conn && virusfilter_shell_set_conn_env(mem_ctx, env_list, conn) == -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 0000000..41e1ec4
--- /dev/null
+++ b/source3/modules/vfs_virusfilter_utils.h
@@ -0,0 +1,166 @@
+/*
+ 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 .
+*/
+
+#ifndef _VIRUSFILTER_UTILS_H
+#define _VIRUSFILTER_UTILS_H
+
+#include "modules/vfs_virusfilter_common.h"
+#include "../lib/util/memcache.h"
+#include "../lib/util/strv.h"
+
+#ifndef VIRUSFILTER_SCAN_RESULTS_CACHE_TALLOC
+#define VIRUSFILTER_SCAN_RESULTS_CACHE_TALLOC SINGLETON_CACHE_TALLOC
+#endif
+
+#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 2
+#define VIRUSFILTER_IO_IOV_MAX 16
+#define VIRUSFILTER_CACHE_BUFFER_SIZE (PATH_MAX + 128)
+
+typedef struct virusfilter_io_handle {
+ int socket;
+ 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;
+ char *r_buffer;
+ char r_buffer_real[VIRUSFILTER_IO_BUFFER_SIZE+1];
+ ssize_t r_size;
+ char *r_rest_buffer;
+ ssize_t r_rest_size;
+} virusfilter_io_handle;
+
+typedef struct virusfilter_cache_entry {
+ time_t time;
+ virusfilter_result result;
+ char *report;
+} virusfilter_cache_entry;
+
+typedef struct virusfilter_cache_handle {
+ struct memcache *cache;
+ TALLOC_CTX *ctx;
+ time_t time_limit;
+} virusfilter_cache_handle;
+
+/* ====================================================================== */
+
+char *virusfilter_string_sub(
+ TALLOC_CTX *mem_ctx,
+ connection_struct *conn,
+ const char *str);
+int virusfilter_url_quote(const char *src, char *dst, int dst_size);
+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 */
+virusfilter_io_handle *virusfilter_io_new(
+ TALLOC_CTX *mem_ctx,
+ int connect_timeout,
+ int timeout);
+int virusfilter_io_set_connect_timeout(
+ virusfilter_io_handle *io_h,
+ int timeout);
+int virusfilter_io_set_io_timeout(virusfilter_io_handle *io_h, int timeout);
+void virusfilter_io_set_writel_eol(
+ virusfilter_io_handle *io_h,
+ const char *eol,
+ int eol_size);
+void virusfilter_io_set_readl_eol(
+ virusfilter_io_handle *io_h,
+ const char *eol,
+ int eol_size);
+virusfilter_result virusfilter_io_connect_path(
+ virusfilter_io_handle *io_h,
+ const char *path);
+virusfilter_result virusfilter_io_disconnect(virusfilter_io_handle *io_h);
+virusfilter_result virusfilter_io_write(
+ virusfilter_io_handle *io_h,
+ const char *data,
+ size_t data_size);
+virusfilter_result virusfilter_io_writel(
+ virusfilter_io_handle *io_h,
+ const char *data,
+ size_t data_size);
+virusfilter_result virusfilter_io_writefl(
+ virusfilter_io_handle *io_h,
+ const char *data_fmt, ...);
+virusfilter_result virusfilter_io_vwritefl(
+ virusfilter_io_handle *io_h,
+ const char *data_fmt, va_list ap);
+virusfilter_result virusfilter_io_writev(virusfilter_io_handle *io_h, ...);
+virusfilter_result virusfilter_io_writevl(virusfilter_io_handle *io_h, ...);
+virusfilter_result virusfilter_io_readl(virusfilter_io_handle *io_h);
+virusfilter_result virusfilter_io_writefl_readl(
+ virusfilter_io_handle *io_h,
+ const char *fmt, ...);
+
+/* Scan result cache */
+virusfilter_cache_handle *virusfilter_cache_new(
+ TALLOC_CTX *ctx,
+ int entry_limit,
+ time_t time_limit);
+int virusfilter_cache_entry_add(
+ virusfilter_cache_handle *cache_h,
+ const char *fname,
+ virusfilter_result result,
+ const char *report);
+int virusfilter_cache_entry_rename(
+ virusfilter_cache_handle *cache_h,
+ const char *old_fname,
+ const char *new_fname);
+void virusfilter_cache_entry_free(virusfilter_cache_entry *cache_e);
+virusfilter_cache_entry *virusfilter_cache_get(
+ virusfilter_cache_handle *cache_h,
+ const char *fname);
+void virusfilter_cache_remove(
+ virusfilter_cache_handle *cache_h,
+ const char *fname);
+void virusfilter_cache_purge(virusfilter_cache_handle *cache_h);
+
+/* 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/vfs_virusfilter_vfs.c b/source3/modules/vfs_virusfilter_vfs.c
new file mode 100644
index 0000000..d701f17
--- /dev/null
+++ b/source3/modules/vfs_virusfilter_vfs.c
@@ -0,0 +1,1406 @@
+/*
+ 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 .
+*/
+
+#ifndef _VIRUSFILTER_VFS_H
+#define _VIRUSFILTER_VFS_H
+
+#include "modules/vfs_virusfilter_common.h"
+#include "modules/vfs_virusfilter_utils.h"
+
+#define VIRUSFILTER_MODULE_NAME "virusfilter_" VIRUSFILTER_MODULE_ENGINE
+#define ALLOC_CHECK(ptr, label) do { if ((ptr) == NULL) { DEBUG(0, \
+ ("virusfilter-vfs: out of memory!\n")); \
+ errno = ENOMEM; goto label; } } while(0)
+
+/* Default configuration values
+ * ====================================================================== */
+
+#define VIRUSFILTER_DEFAULT_SCAN_ON_OPEN true
+#define VIRUSFILTER_DEFAULT_SCAN_ON_CLOSE false
+#define VIRUSFILTER_DEFAULT_MAX_FILE_SIZE 100000000L /* 100MB */
+#define VIRUSFILTER_DEFAULT_MIN_FILE_SIZE 0
+#define VIRUSFILTER_DEFAULT_EXCLUDE_FILES NULL
+
+#define VIRUSFILTER_DEFAULT_CACHE_ENTRY_LIMIT 100
+#define VIRUSFILTER_DEFAULT_CACHE_TIME_LIMIT 10
+
+#define VIRUSFILTER_DEFAULT_INFECTED_FILE_ACTION \
+ VIRUSFILTER_ACTION_DO_NOTHING
+#define VIRUSFILTER_DEFAULT_INFECTED_FILE_COMMAND NULL
+#define VIRUSFILTER_DEFAULT_INFECTED_FILE_ERRNO_ON_OPEN EACCES
+#define VIRUSFILTER_DEFAULT_INFECTED_FILE_ERRNO_ON_CLOSE 0
+
+#define VIRUSFILTER_DEFAULT_SCAN_ERROR_COMMAND NULL
+#define VIRUSFILTER_DEFAULT_SCAN_ERROR_ERRNO_ON_OPEN EACCES
+#define VIRUSFILTER_DEFAULT_SCAN_ERROR_ERRNO_ON_CLOSE 0
+#define VIRUSFILTER_DEFAULT_BLOCK_ACCESS_ON_ERROR false
+
+#define VIRUSFILTER_DEFAULT_QUARANTINE_DIRECTORY VARDIR \
+ "/virusfilter/quarantine"
+#define VIRUSFILTER_DEFAULT_QUARANTINE_PREFIX "virusfilter."
+#define VIRUSFILTER_DEFAULT_QUARANTINE_SUFFIX ".infected"
+#define VIRUSFILTER_DEFAULT_QUARANTINE_KEEP_NAME false
+#define VIRUSFILTER_DEFAULT_QUARANTINE_KEEP_TREE false
+/* 700 = S_IRUSR | S_IWUSR | S_IXUSR */
+#define VIRUSFILTER_DEFAULT_QUARANTINE_DIR_MODE "700"
+
+#define VIRUSFILTER_DEFAULT_RENAME_PREFIX "virusfilter."
+#define VIRUSFILTER_DEFAULT_RENAME_SUFFIX ".infected"
+
+/* ====================================================================== */
+
+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}
+};
+
+typedef struct {
+#ifdef VIRUSFILTER_DEFAULT_SCAN_REQUEST_LIMIT
+ int scan_request_count;
+ int scan_request_limit;
+#endif
+ /* Scan on file operations */
+ bool scan_on_open;
+ bool scan_on_close;
+ /* Special scan options */
+#ifdef VIRUSFILTER_DEFAULT_SCAN_ARCHIVE
+ bool scan_archive;
+#endif
+#ifdef VIRUSFILTER_DEFAULT_MAX_NESTED_SCAN_ARCHIVE
+ int max_nested_scan_archive;
+#endif
+#ifdef VIRUSFILTER_DEFAULT_SCAN_MIME
+ bool scan_mime;
+#endif
+ /* Size limit */
+ ssize_t max_file_size;
+ ssize_t min_file_size;
+ /* Exclude files */
+ name_compare_entry *exclude_files;
+ /* Scan result cache */
+ virusfilter_cache_handle *cache_h;
+ 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_name;
+ bool quarantine_keep_tree;
+ mode_t quarantine_dir_mode;
+ /* Rename infected files */
+ const char * rename_prefix;
+ const char * rename_suffix;
+ /* Network options */
+#ifdef VIRUSFILTER_DEFAULT_SOCKET_PATH
+ const char * socket_path;
+ virusfilter_io_handle *io_h;
+#endif
+ /* Module specific configuration options */
+#ifdef VIRUSFILTER_MODULE_CONFIG_MEMBERS
+ VIRUSFILTER_MODULE_CONFIG_MEMBERS
+#endif
+} virusfilter_handle;
+
+/* ====================================================================== */
+
+#ifdef virusfilter_module_connect
+static int virusfilter_module_connect(
+ vfs_handle_struct *vfs_h,
+ virusfilter_handle *virusfilter_h,
+ const char *svc,
+ const char *user);
+#endif
+
+#ifdef virusfilter_module_disconnect
+static int virusfilter_module_disconnect(vfs_handle_struct *vfs_h);
+#endif
+
+#ifdef virusfilter_module_destruct_config
+static int virusfilter_module_destruct_config(
+ virusfilter_handle *virusfilter_h);
+#endif
+
+#ifdef virusfilter_module_scan_init
+static virusfilter_result virusfilter_module_scan_init(
+ virusfilter_handle *virusfilter_h);
+#endif
+
+#ifdef virusfilter_module_scan_end
+static void virusfilter_module_scan_end(virusfilter_handle *virusfilter_h);
+#endif
+
+static virusfilter_result virusfilter_module_scan(
+ vfs_handle_struct *vfs_h,
+ virusfilter_handle *virusfilter_h,
+ const struct smb_filename *smb_fname,
+ const char **reportp);
+
+/* ====================================================================== */
+
+static int virusfilter_destruct_config(virusfilter_handle *virusfilter_h)
+{
+#ifdef virusfilter_module_destruct_config
+ return virusfilter_module_destruct_config(virusfilter_h);
+#else
+ return 0;
+#endif
+}
+
+// This is adapted from vfs_recycle module.
+static bool quarantine_directory_exist(
+ vfs_handle_struct *handle,
+ const char *dname)
+{
+ struct smb_filename smb_fname = {
+ .base_name = discard_const_p(char, dname)
+ };
+
+ if (SMB_VFS_STAT(handle->conn, &smb_fname) == 0) {
+ if (S_ISDIR(smb_fname.st.st_ex_mode)) {
+ return True;
+ }
+ }
+
+ 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.
+ **/
+static bool quarantine_create_dir(
+ vfs_handle_struct *handle,
+ virusfilter_handle *virusfilter_h,
+ const char *dname)
+{
+ size_t len;
+ mode_t mode;
+ char *new_dir = NULL;
+ char *tmp_str = NULL;
+ char *token;
+ char *tok_str;
+ bool ret = False;
+ char *saveptr;
+
+ mode = virusfilter_h->quarantine_dir_mode;
+
+ tmp_str = talloc_strdup(talloc_tos(), dname);
+ ALLOC_CHECK(tmp_str, done);
+ tok_str = tmp_str;
+
+ len = strlen(dname)+1;
+ new_dir = (char *)talloc_size(talloc_tos(), len + 1);
+ ALLOC_CHECK(new_dir, done);
+ *new_dir = '\0';
+ if (dname[0] == '/') {
+ /* Absolute path. */
+ if (strlcat(new_dir,"/",len+1) >= len+1) {
+ goto done;
+ }
+ }
+
+ become_root();
+ /* Create directory tree if neccessary */
+ for(token = strtok_r(tok_str, "/", &saveptr); token;
+ token = strtok_r(NULL, "/", &saveptr)) {
+ if (strlcat(new_dir, token, len+1) >= len+1) {
+ goto done;
+ }
+ if (quarantine_directory_exist(handle, new_dir)) {
+ DEBUG(10, ("quarantine: dir %s already exists\n",
+ new_dir));
+ } else {
+#if SAMBA_VERSION_NUMBER >= 40500
+ struct smb_filename *smb_fname = NULL;
+#endif
+
+ DEBUG(5, ("quarantine: creating new dir %s\n",
+ new_dir));
+
+#if SAMBA_VERSION_NUMBER < 40500
+ if (SMB_VFS_NEXT_MKDIR(handle, new_dir, mode) != 0) {
+#else
+ smb_fname = synthetic_smb_fname(talloc_tos(),
+ new_dir,
+ NULL, NULL,
+ 0);
+ if (smb_fname == NULL) {
+ goto done;
+ }
+
+ if (SMB_VFS_NEXT_MKDIR(handle, smb_fname, mode) != 0) {
+ TALLOC_FREE(smb_fname);
+#endif
+ DEBUG(1,("quarantine: mkdir failed for %s "
+ "with error: %s\n", new_dir,
+ strerror(errno)));
+ ret = False;
+ goto done;
+ }
+#if SAMBA_VERSION_NUMBER >= 40500
+ TALLOC_FREE(smb_fname);
+#endif
+ }
+ if (strlcat(new_dir, "/", len+1) >= len+1) {
+ goto done;
+ }
+ mode = virusfilter_h->quarantine_dir_mode;
+ }
+
+ ret = True;
+ done:
+ unbecome_root();
+ TALLOC_FREE(tmp_str);
+ TALLOC_FREE(new_dir);
+ return ret;
+}
+
+static int virusfilter_vfs_connect(
+ vfs_handle_struct *vfs_h,
+ const char *svc,
+ const char *user)
+{
+ int snum = SNUM(vfs_h->conn);
+ virusfilter_handle *virusfilter_h;
+ const char *exclude_files;
+ const char *temp_quarantine_dir_mode = NULL;
+#ifdef VIRUSFILTER_DEFAULT_SOCKET_PATH
+ int connect_timeout, io_timeout;
+#endif
+
+
+ virusfilter_h = talloc_zero(vfs_h, virusfilter_handle);
+ if (!virusfilter_h) {
+ DEBUG(0, ("talloc_zero failed\n"));
+ return -1;
+ }
+
+ talloc_set_destructor(virusfilter_h, virusfilter_destruct_config);
+
+ SMB_VFS_HANDLE_SET_DATA(vfs_h, virusfilter_h, NULL, virusfilter_handle,
+ return -1);
+
+#ifdef VIRUSFILTER_DEFAULT_SCAN_REQUEST_LIMIT
+ virusfilter_h->scan_request_limit = lp_parm_int(
+ snum, VIRUSFILTER_MODULE_NAME,
+ "scan request limit",
+ VIRUSFILTER_DEFAULT_SCAN_REQUEST_LIMIT);
+#endif
+
+ virusfilter_h->scan_on_open = lp_parm_bool(snum,
+ VIRUSFILTER_MODULE_NAME, "scan on open",
+ VIRUSFILTER_DEFAULT_SCAN_ON_OPEN);
+ virusfilter_h->scan_on_close = lp_parm_bool(snum,
+ VIRUSFILTER_MODULE_NAME, "scan on close",
+ VIRUSFILTER_DEFAULT_SCAN_ON_CLOSE);
+#ifdef VIRUSFILTER_DEFAULT_MAX_NESTED_SCAN_ARCHIVE
+ virusfilter_h->max_nested_scan_archive = lp_parm_int(snum,
+ VIRUSFILTER_MODULE_NAME, "max nested scan archive",
+ VIRUSFILTER_DEFAULT_MAX_NESTED_SCAN_ARCHIVE);
+#endif
+#ifdef VIRUSFILTER_DEFAULT_SCAN_ARCHIVE
+ virusfilter_h->scan_archive = lp_parm_bool(snum,
+ VIRUSFILTER_MODULE_NAME, "scan archive",
+ VIRUSFILTER_DEFAULT_SCAN_ARCHIVE);
+#endif
+#ifdef VIRUSFILTER_DEFAULT_MIME_SCAN
+ virusfilter_h->scan_mime = lp_parm_bool(snum,
+ VIRUSFILTER_MODULE_NAME, "scan mime",
+ VIRUSFILTER_DEFAULT_SCAN_MIME);
+#endif
+
+ virusfilter_h->max_file_size = (ssize_t)lp_parm_ulong(snum,
+ VIRUSFILTER_MODULE_NAME, "max file size",
+ VIRUSFILTER_DEFAULT_MAX_FILE_SIZE);
+ virusfilter_h->min_file_size = (ssize_t)lp_parm_ulong(snum,
+ VIRUSFILTER_MODULE_NAME, "min file size",
+ VIRUSFILTER_DEFAULT_MIN_FILE_SIZE);
+
+ exclude_files = lp_parm_const_string(snum,
+ VIRUSFILTER_MODULE_NAME, "exclude files",
+ VIRUSFILTER_DEFAULT_EXCLUDE_FILES);
+ if (exclude_files) {
+ set_namearray(&virusfilter_h->exclude_files, exclude_files);
+ }
+
+ virusfilter_h->cache_entry_limit = lp_parm_int(snum,
+ VIRUSFILTER_MODULE_NAME, "cache entry limit",
+ VIRUSFILTER_DEFAULT_CACHE_ENTRY_LIMIT);
+ virusfilter_h->cache_time_limit = lp_parm_int(snum,
+ VIRUSFILTER_MODULE_NAME, "cache time limit",
+ VIRUSFILTER_DEFAULT_CACHE_TIME_LIMIT);
+
+ virusfilter_h->infected_file_action = lp_parm_enum(snum,
+ VIRUSFILTER_MODULE_NAME, "infected file action",
+ virusfilter_actions, VIRUSFILTER_DEFAULT_INFECTED_FILE_ACTION);
+ virusfilter_h->infected_file_command = lp_parm_const_string(snum,
+ VIRUSFILTER_MODULE_NAME, "infected file command",
+ VIRUSFILTER_DEFAULT_INFECTED_FILE_COMMAND);
+ virusfilter_h->scan_error_command = lp_parm_const_string(snum,
+ VIRUSFILTER_MODULE_NAME, "scan error command",
+ VIRUSFILTER_DEFAULT_SCAN_ERROR_COMMAND);
+ virusfilter_h->block_access_on_error = lp_parm_bool(snum,
+ VIRUSFILTER_MODULE_NAME, "block access on error",
+ VIRUSFILTER_DEFAULT_BLOCK_ACCESS_ON_ERROR);
+
+ virusfilter_h->quarantine_dir = lp_parm_const_string(snum,
+ VIRUSFILTER_MODULE_NAME, "quarantine directory",
+ VIRUSFILTER_DEFAULT_QUARANTINE_DIRECTORY);
+ temp_quarantine_dir_mode = lp_parm_const_string(snum,
+ VIRUSFILTER_MODULE_NAME, "quarantine directory mode",
+ VIRUSFILTER_DEFAULT_QUARANTINE_DIR_MODE);
+ if (temp_quarantine_dir_mode != NULL) {
+ sscanf(temp_quarantine_dir_mode, "%o",
+ &virusfilter_h->quarantine_dir_mode);
+ }
+ virusfilter_h->quarantine_prefix = lp_parm_const_string(snum,
+ VIRUSFILTER_MODULE_NAME, "quarantine prefix",
+ VIRUSFILTER_DEFAULT_QUARANTINE_PREFIX);
+ virusfilter_h->quarantine_suffix = lp_parm_const_string(snum,
+ VIRUSFILTER_MODULE_NAME, "quarantine suffix",
+ VIRUSFILTER_DEFAULT_QUARANTINE_SUFFIX);
+ virusfilter_h->quarantine_keep_tree = lp_parm_bool(snum,
+ VIRUSFILTER_MODULE_NAME, "quarantine keep tree",
+ VIRUSFILTER_DEFAULT_QUARANTINE_KEEP_TREE);
+ virusfilter_h->quarantine_keep_name = lp_parm_bool(snum,
+ VIRUSFILTER_MODULE_NAME, "quarantine keep name",
+ VIRUSFILTER_DEFAULT_QUARANTINE_KEEP_NAME);
+
+ virusfilter_h->rename_prefix = lp_parm_const_string(snum,
+ VIRUSFILTER_MODULE_NAME, "rename prefix",
+ VIRUSFILTER_DEFAULT_RENAME_PREFIX);
+ virusfilter_h->rename_suffix = lp_parm_const_string(snum,
+ VIRUSFILTER_MODULE_NAME, "rename suffix",
+ VIRUSFILTER_DEFAULT_RENAME_SUFFIX);
+
+ virusfilter_h->infected_open_errno = lp_parm_int(snum,
+ VIRUSFILTER_MODULE_NAME, "infected file errno on open",
+ VIRUSFILTER_DEFAULT_INFECTED_FILE_ERRNO_ON_OPEN);
+ virusfilter_h->infected_close_errno = lp_parm_int(snum,
+ VIRUSFILTER_MODULE_NAME, "infected file errno on close",
+ VIRUSFILTER_DEFAULT_INFECTED_FILE_ERRNO_ON_CLOSE);
+ virusfilter_h->scan_error_open_errno = lp_parm_int(snum,
+ VIRUSFILTER_MODULE_NAME, "scan error errno on open",
+ VIRUSFILTER_DEFAULT_SCAN_ERROR_ERRNO_ON_OPEN);
+ virusfilter_h->scan_error_close_errno = lp_parm_int(snum,
+ VIRUSFILTER_MODULE_NAME, "scan error errno on close",
+ VIRUSFILTER_DEFAULT_SCAN_ERROR_ERRNO_ON_CLOSE);
+
+#ifdef VIRUSFILTER_DEFAULT_SOCKET_PATH
+ virusfilter_h->socket_path = lp_parm_const_string(snum,
+ VIRUSFILTER_MODULE_NAME, "socket path",
+ VIRUSFILTER_DEFAULT_SOCKET_PATH);
+ connect_timeout = lp_parm_int(snum, VIRUSFILTER_MODULE_NAME,
+ "connect timeout", VIRUSFILTER_DEFAULT_CONNECT_TIMEOUT);
+ io_timeout = lp_parm_int(snum, VIRUSFILTER_MODULE_NAME, "io timeout",
+ VIRUSFILTER_DEFAULT_TIMEOUT);
+
+ virusfilter_h->io_h =
+ virusfilter_io_new(virusfilter_h, connect_timeout, io_timeout);
+
+ if (!virusfilter_h->io_h) {
+ DEBUG(0,("virusfilter_io_new failed"));
+ return -1;
+ }
+#endif
+
+ if (virusfilter_h->cache_entry_limit > 0) {
+ virusfilter_h->cache_h = virusfilter_cache_new(vfs_h,
+ virusfilter_h->cache_entry_limit,
+ virusfilter_h->cache_time_limit);
+ if (!virusfilter_h->cache_h) {
+ DEBUG(0,("Initializing cache failed: Cache "
+ "disabled\n"));
+ }
+ }
+
+#ifdef virusfilter_module_connect
+ if (virusfilter_module_connect(vfs_h, virusfilter_h, svc, user) == -1) {
+ return -1;
+ }
+#endif
+
+ /* Check quarantine directory now to save processing
+ * and becoming root over and over. */
+ if (virusfilter_h->infected_file_action ==
+ VIRUSFILTER_ACTION_QUARANTINE)
+ {
+ // Do SMB_VFS_NEXT_MKDIR(virusfilter_h->quarantine_dir)
+ // hierarchy
+ become_root();
+ if (!quarantine_directory_exist(vfs_h,
+ virusfilter_h->quarantine_dir))
+ {
+ unbecome_root();
+ DEBUG(10, ("Creating quarantine directory: %s\n",
+ virusfilter_h->quarantine_dir));
+ quarantine_create_dir(vfs_h, virusfilter_h,
+ virusfilter_h->quarantine_dir);
+ } else {
+ unbecome_root();
+ }
+ }
+
+ return SMB_VFS_NEXT_CONNECT(vfs_h, svc, user);
+}
+
+static void virusfilter_vfs_disconnect(vfs_handle_struct *vfs_h)
+{
+ virusfilter_handle *virusfilter_h;
+
+#ifdef virusfilter_module_disconnect
+ virusfilter_module_disconnect(vfs_h);
+#endif
+
+ SMB_VFS_HANDLE_GET_DATA(vfs_h, virusfilter_h, virusfilter_handle,
+ return);
+
+ free_namearray(virusfilter_h->exclude_files);
+#ifdef VIRUSFILTER_DEFAULT_SOCKET_PATH
+ virusfilter_io_disconnect(virusfilter_h->io_h);
+#endif
+
+ SMB_VFS_NEXT_DISCONNECT(vfs_h);
+}
+
+static int virusfilter_set_module_env(TALLOC_CTX *mem_ctx, char **env_list)
+{
+ if (virusfilter_env_set(mem_ctx, env_list, "VIRUSFILTER_VERSION",
+ VIRUSFILTER_VERSION) == -1)
+ {
+ return -1;
+ }
+ if (virusfilter_env_set(mem_ctx, env_list, "VIRUSFILTER_MODULE_NAME",
+ VIRUSFILTER_MODULE_NAME) == -1)
+ {
+ return -1;
+ }
+#ifdef VIRUSFILTER_MODULE_VERSION
+ if (virusfilter_env_set(mem_ctx, env_list,
+ "VIRUSFILTER_MODULE_VERSION",
+ VIRUSFILTER_MODULE_VERSION) == -1)
+ {
+ return -1;
+ }
+#endif
+
+ return 0;
+}
+
+static virusfilter_action virusfilter_do_infected_file_action(
+ vfs_handle_struct *vfs_h,
+ virusfilter_handle *virusfilter_h,
+ const struct smb_filename *smb_fname,
+ const char **filepath_newp)
+{
+ TALLOC_CTX *mem_ctx = talloc_tos();
+ connection_struct *conn = vfs_h->conn;
+ struct smb_filename *q_smb_fname = NULL;
+ char *q_dir;
+ char *q_prefix;
+ char *q_suffix;
+ char *q_filepath;
+ char *dir_name = NULL;
+ char *temp_path;
+ const char *base_name = NULL;
+ int q_fd;
+
+ *filepath_newp = NULL;
+
+ switch (virusfilter_h->infected_file_action) {
+ case VIRUSFILTER_ACTION_RENAME:
+ q_prefix = virusfilter_string_sub(mem_ctx, conn,
+ virusfilter_h->rename_prefix);
+ q_suffix = virusfilter_string_sub(mem_ctx, conn,
+ virusfilter_h->rename_suffix);
+ if (q_prefix == NULL || q_suffix == NULL) {
+ DEBUG(0,("Rename failed: %s/%s: "
+ "Cannot allocate memory\n",
+ conn->connectpath,
+ smb_fname->base_name));
+ TALLOC_FREE(q_prefix);
+ TALLOC_FREE(q_suffix);
+ return VIRUSFILTER_ACTION_DO_NOTHING;
+ }
+
+ if (!parent_dirname(mem_ctx, smb_fname->base_name, &q_dir,
+ &base_name))
+ {
+ DEBUG(0,("Rename failed: %s/%s: "
+ "Cannot allocate memory\n",
+ conn->connectpath,
+ smb_fname->base_name));
+ TALLOC_FREE(q_prefix);
+ TALLOC_FREE(q_suffix);
+ return VIRUSFILTER_ACTION_DO_NOTHING;
+ }
+
+ if (q_dir == NULL) {
+ DEBUG(0,("Rename failed: %s/%s: "
+ "Cannot allocate memory\n",
+ conn->connectpath,
+ smb_fname->base_name));
+ TALLOC_FREE(q_prefix);
+ TALLOC_FREE(q_suffix);
+ return VIRUSFILTER_ACTION_DO_NOTHING;
+ }
+
+ q_filepath = talloc_asprintf(talloc_tos(), "%s/%s%s%s", q_dir,
+ q_prefix, base_name, q_suffix);
+
+ TALLOC_FREE(q_dir);
+ TALLOC_FREE(q_prefix);
+ TALLOC_FREE(q_suffix);
+
+ become_root();
+
+#if SAMBA_VERSION_NUMBER >= 40500
+ q_smb_fname = synthetic_smb_fname(mem_ctx, q_filepath,
+ smb_fname->stream_name, NULL, smb_fname->flags);
+ if (q_smb_fname == NULL) {
+#elif SAMBA_VERSION_NUMBER >= 40100
+ q_smb_fname = synthetic_smb_fname(mem_ctx, q_filepath,
+ smb_fname->stream_name, NULL);
+ if (q_smb_fname == NULL) {
+#else
+ NTSTATUS status;
+ status = create_synthetic_smb_fname(mem_ctx,
+ q_filepath,
+ smb_fname->stream_name,
+ NULL,
+ &q_smb_fname);
+ if (!NT_STATUS_IS_OK(status)) {
+#endif
+ unlink(q_filepath);
+ unbecome_root();
+ return VIRUSFILTER_ACTION_DO_NOTHING;
+ }
+
+ if (virusfilter_vfs_next_move(vfs_h, smb_fname, q_smb_fname)
+ == -1)
+ {
+ unbecome_root();
+ DEBUG(0,("Rename failed: %s/%s: Rename failed: %s\n",
+ conn->connectpath,
+ smb_fname->base_name,
+ strerror(errno)));
+ return VIRUSFILTER_ACTION_DO_NOTHING;
+ }
+ unbecome_root();
+
+ *filepath_newp = q_filepath;
+
+ return VIRUSFILTER_ACTION_RENAME;
+
+ case VIRUSFILTER_ACTION_QUARANTINE:
+ q_dir = virusfilter_string_sub(mem_ctx, conn,
+ virusfilter_h->quarantine_dir);
+ q_prefix = virusfilter_string_sub(mem_ctx, conn,
+ virusfilter_h->quarantine_prefix);
+ q_suffix = virusfilter_string_sub(mem_ctx, conn,
+ virusfilter_h->quarantine_suffix);
+ if (q_dir == NULL || q_prefix == NULL || q_suffix == NULL) {
+ DEBUG(0,("Quarantine failed: %s/%s: "
+ "Cannot allocate memory\n",
+ conn->connectpath,
+ smb_fname->base_name));
+ TALLOC_FREE(q_dir);
+ TALLOC_FREE(q_prefix);
+ TALLOC_FREE(q_suffix);
+ return VIRUSFILTER_ACTION_DO_NOTHING;
+ }
+
+ if (virusfilter_h->quarantine_keep_name ||
+ virusfilter_h->quarantine_keep_tree)
+ {
+ if (!parent_dirname(mem_ctx, smb_fname->base_name,
+ &dir_name, &base_name))
+ {
+ DEBUG(0,("Quarantine failed: %s/%s: "
+ "Cannot allocate memory\n",
+ conn->connectpath,
+ smb_fname->base_name));
+ TALLOC_FREE(q_dir);
+ TALLOC_FREE(q_prefix);
+ TALLOC_FREE(q_suffix);
+ return VIRUSFILTER_ACTION_DO_NOTHING;
+ }
+
+ if (virusfilter_h->quarantine_keep_tree) {
+ temp_path = talloc_asprintf(mem_ctx, "%s/%s",
+ q_dir, dir_name);
+ if (temp_path == NULL)
+ {
+ DEBUG(0,("Quarantine failed: %s/%s: "
+ "Cannot allocate memory\n",
+ conn->connectpath,
+ smb_fname->base_name));
+ TALLOC_FREE(q_dir);
+ TALLOC_FREE(q_prefix);
+ TALLOC_FREE(q_suffix);
+ return VIRUSFILTER_ACTION_DO_NOTHING;
+ }
+
+ become_root();
+ if (quarantine_directory_exist(vfs_h,
+ temp_path))
+ {
+ DEBUG(10, ("quarantine: Directory "
+ "already exists\n"));
+ TALLOC_FREE(q_dir);
+ q_dir = temp_path;
+ } else {
+ DEBUG(10, ("quarantine: Creating "
+ "directory %s\n", temp_path));
+ if (quarantine_create_dir(vfs_h,
+ virusfilter_h, temp_path) == False)
+ {
+ DEBUG(3, ("quarantine: Could "
+ "not create directory "
+ "ignoring for %s...\n",
+ smb_fname_str_dbg(
+ smb_fname)));
+ TALLOC_FREE(temp_path);
+ } else {
+ TALLOC_FREE(q_dir);
+ q_dir = temp_path;
+ }
+ }
+ unbecome_root();
+ }
+ }
+ if (virusfilter_h->quarantine_keep_name) {
+ q_filepath = talloc_asprintf(talloc_tos(),
+ "%s/%s%s%s-XXXXXX", q_dir, q_prefix, base_name,
+ q_suffix);
+ } else {
+ q_filepath = talloc_asprintf(talloc_tos(),
+ "%s/%sXXXXXX", q_dir, q_prefix);
+ }
+
+ TALLOC_FREE(dir_name);
+ TALLOC_FREE(q_dir);
+ TALLOC_FREE(q_prefix);
+ TALLOC_FREE(q_suffix);
+
+ if (q_filepath == NULL) {
+ DEBUG(0,("Quarantine failed: %s/%s: "
+ "Cannot allocate memory\n",
+ conn->connectpath,
+ smb_fname->base_name));
+ return VIRUSFILTER_ACTION_DO_NOTHING;
+ }
+
+ become_root();
+
+ q_fd = mkstemp(q_filepath);
+ if (q_fd == -1) {
+ unbecome_root();
+ DEBUG(0,("Quarantine failed: %s/%s: "
+ "Cannot open destination: %s: %s\n",
+ conn->connectpath,
+ smb_fname->base_name,
+ q_filepath, strerror(errno)));
+ return VIRUSFILTER_ACTION_DO_NOTHING;
+ }
+ close(q_fd);
+
+#if SAMBA_VERSION_NUMBER >= 40500
+ q_smb_fname = synthetic_smb_fname(mem_ctx, q_filepath,
+ smb_fname->stream_name, NULL, smb_fname->flags);
+ if (q_smb_fname == NULL) {
+#elif SAMBA_VERSION_NUMBER >= 40100
+ q_smb_fname = synthetic_smb_fname(mem_ctx, q_filepath,
+ smb_fname->stream_name, NULL);
+ if (q_smb_fname == NULL) {
+#else
+ NTSTATUS status;
+ status = create_synthetic_smb_fname(mem_ctx,
+ q_filepath,
+ smb_fname->stream_name,
+ NULL,
+ &q_smb_fname);
+ if (!NT_STATUS_IS_OK(status)) {
+#endif
+ unlink(q_filepath);
+ unbecome_root();
+ return VIRUSFILTER_ACTION_DO_NOTHING;
+ }
+
+ if (virusfilter_vfs_next_move(vfs_h, smb_fname, q_smb_fname)
+ == -1)
+ {
+ unbecome_root();
+ DEBUG(0,("Quarantine failed: %s/%s: Rename failed: "
+ "%s\n", conn->connectpath,
+ smb_fname->base_name,
+ strerror(errno)));
+ return VIRUSFILTER_ACTION_DO_NOTHING;
+ }
+ unbecome_root();
+
+ *filepath_newp = q_filepath;
+
+ return VIRUSFILTER_ACTION_QUARANTINE;
+
+ case VIRUSFILTER_ACTION_DELETE:
+ become_root();
+ if (SMB_VFS_NEXT_UNLINK(vfs_h, smb_fname) == -1) {
+ unbecome_root();
+ DEBUG(0,("Delete failed: %s/%s: Unlink failed: %s\n",
+ conn->connectpath,
+ smb_fname->base_name,
+ strerror(errno)));
+ return VIRUSFILTER_ACTION_DO_NOTHING;
+ }
+ unbecome_root();
+ return VIRUSFILTER_ACTION_DELETE;
+
+ case VIRUSFILTER_ACTION_DO_NOTHING:
+ default:
+ return VIRUSFILTER_ACTION_DO_NOTHING;
+ }
+}
+
+static virusfilter_action virusfilter_treat_infected_file(
+ vfs_handle_struct *vfs_h,
+ virusfilter_handle *virusfilter_h,
+ const struct smb_filename *smb_fname,
+ const char *report,
+ bool is_cache)
+{
+ connection_struct *conn = vfs_h->conn;
+ 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;
+
+ action = virusfilter_do_infected_file_action(vfs_h, virusfilter_h,
+ smb_fname, &filepath_q);
+ for (i=0; virusfilter_actions[i].name; i++) {
+ if (virusfilter_actions[i].value == action) {
+ action_name = virusfilter_actions[i].name;
+ break;
+ }
+ }
+ DEBUG(1,("Infected file action: %s/%s: %s\n",
+ vfs_h->conn->connectpath,
+ smb_fname->base_name,
+ action_name));
+
+ if (!virusfilter_h->infected_file_command) {
+ return action;
+ }
+
+ if (virusfilter_set_module_env(mem_ctx, &env_list) == -1) {
+ goto done;
+ }
+ if (virusfilter_env_set(mem_ctx, &env_list,
+ "VIRUSFILTER_INFECTED_SERVICE_FILE_PATH",
+ smb_fname->base_name) == -1)
+ {
+ goto done;
+ }
+ if (report && virusfilter_env_set(mem_ctx, &env_list,
+ "VIRUSFILTER_INFECTED_FILE_REPORT", report) == -1)
+ {
+ goto done;
+ }
+ if (virusfilter_env_set(mem_ctx, &env_list, "VIRUSFILTER_INFECTED_FILE_ACTION",
+ action_name) == -1)
+ {
+ goto done;
+ }
+ if (filepath_q && virusfilter_env_set(mem_ctx, &env_list,
+ "VIRUSFILTER_QUARANTINED_FILE_PATH", filepath_q) == -1)
+ {
+ goto done;
+ }
+ if (is_cache && virusfilter_env_set(mem_ctx, &env_list,
+ "VIRUSFILTER_RESULT_IS_CACHE",
+ "yes") == -1)
+ {
+ goto done;
+ }
+
+ command = virusfilter_string_sub(mem_ctx, conn,
+ virusfilter_h->infected_file_command);
+ if (command == NULL) {
+ DEBUG(0,("virusfilter_string_sub failed\n"));
+ goto done;
+ }
+
+ DEBUG(3,("Infected file command line: %s/%s: %s\n",
+ vfs_h->conn->connectpath, smb_fname->base_name, command));
+
+ command_result = virusfilter_shell_run(mem_ctx, command, &env_list,
+ vfs_h->conn, true);
+ if (command_result != 0) {
+ DEBUG(0,("Infected file command failed: %d\n",
+ command_result));
+ }
+
+ DEBUG(10,("Infected file command finished: %d\n", command_result));
+
+done:
+ TALLOC_FREE(env_list);
+ TALLOC_FREE(command);
+
+ return action;
+}
+
+static void virusfilter_treat_scan_error(
+ vfs_handle_struct *vfs_h,
+ virusfilter_handle *virusfilter_h,
+ const struct smb_filename *smb_fname,
+ const char *report,
+ bool is_cache)
+{
+ connection_struct *conn = vfs_h->conn;
+ TALLOC_CTX *mem_ctx = talloc_tos();
+ char *env_list = NULL;
+ char *command = NULL;
+ int command_result;
+
+ if (!virusfilter_h->scan_error_command) {
+ return;
+ }
+ if (virusfilter_set_module_env(mem_ctx, &env_list) == -1) {
+ goto done;
+ }
+ if (virusfilter_env_set(mem_ctx, &env_list,
+ "VIRUSFILTER_SCAN_ERROR_SERVICE_FILE_PATH",
+ smb_fname->base_name) == -1)
+ {
+ goto done;
+ }
+ if (report && virusfilter_env_set(mem_ctx, &env_list,
+ "VIRUSFILTER_SCAN_ERROR_REPORT", report) == -1)
+ {
+ goto done;
+ }
+ if (is_cache && virusfilter_env_set(mem_ctx, &env_list,
+ "VIRUSFILTER_RESULT_IS_CACHE", "1") == -1)
+ {
+ goto done;
+ }
+
+ command = virusfilter_string_sub(mem_ctx, conn,
+ virusfilter_h->scan_error_command);
+ if (!command) {
+ DEBUG(0,("virusfilter_string_sub failed\n"));
+ goto done;
+ }
+
+ DEBUG(3,("Scan error command line: %s/%s: %s\n",
+ vfs_h->conn->connectpath, smb_fname->base_name, command));
+
+ command_result = virusfilter_shell_run(mem_ctx, command, &env_list,
+ vfs_h->conn, true);
+ if (command_result != 0) {
+ DEBUG(0,("Scan error command failed: %d\n", command_result));
+ }
+
+done:
+ TALLOC_FREE(env_list);
+ TALLOC_FREE(command);
+}
+
+static virusfilter_result virusfilter_scan(
+ vfs_handle_struct *vfs_h,
+ virusfilter_handle *virusfilter_h,
+ const struct smb_filename *smb_fname)
+{
+ virusfilter_result scan_result;
+ const char *scan_report = NULL;
+ char *fname = smb_fname->base_name;
+ virusfilter_cache_entry *scan_cache_e = NULL;
+ bool is_cache = false;
+ virusfilter_action file_action = VIRUSFILTER_ACTION_DO_NOTHING;
+ bool add_scan_cache = true;
+
+ if (virusfilter_h->cache_h) {
+ DEBUG(10, ("Searching cache entry: fname: %s\n", fname));
+ scan_cache_e = virusfilter_cache_get(virusfilter_h->cache_h,
+ fname);
+ if (scan_cache_e) {
+ DEBUG(10, ("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;
+ }
+ DEBUG(10, ("Cache entry not found\n"));
+ }
+
+#ifdef virusfilter_module_scan_init
+ if (virusfilter_module_scan_init(virusfilter_h) !=
+ VIRUSFILTER_RESULT_OK)
+ {
+ scan_result = VIRUSFILTER_RESULT_ERROR;
+ scan_report = "Initializing scanner failed";
+ goto virusfilter_scan_result_eval;
+ }
+#endif
+
+ scan_result = virusfilter_module_scan(vfs_h, virusfilter_h, smb_fname,
+ &scan_report);
+
+#ifdef virusfilter_module_scan_end
+#ifdef VIRUSFILTER_DEFAULT_SCAN_REQUEST_LIMIT
+ if (virusfilter_h->scan_request_limit > 0) {
+ virusfilter_h->scan_request_count++;
+ if (virusfilter_h->scan_request_count >=
+ virusfilter_h->scan_request_limit)
+ {
+ virusfilter_module_scan_end(virusfilter_h);
+ virusfilter_h->scan_request_count = 0;
+ }
+ }
+#else
+ virusfilter_module_scan_end(virusfilter_h);
+#endif
+#endif
+
+virusfilter_scan_result_eval:
+
+ switch (scan_result) {
+ case VIRUSFILTER_RESULT_CLEAN:
+ DEBUG(5, ("Scan result: Clean: %s/%s\n",
+ vfs_h->conn->connectpath,
+ fname));
+ break;
+ case VIRUSFILTER_RESULT_INFECTED:
+ DEBUG(0, ("Scan result: Infected: %s/%s: %s\n",
+ vfs_h->conn->connectpath,
+ fname,
+ scan_report));
+ file_action = virusfilter_treat_infected_file(vfs_h,
+ virusfilter_h, smb_fname,
+ scan_report, is_cache);
+ if (file_action != VIRUSFILTER_ACTION_DO_NOTHING) {
+ add_scan_cache = false;
+ }
+ break;
+ case VIRUSFILTER_RESULT_ERROR:
+ DEBUG(0, ("Scan result: Error: %s/%s: %s\n",
+ vfs_h->conn->connectpath,
+ fname,
+ scan_report));
+ virusfilter_treat_scan_error(vfs_h, virusfilter_h, smb_fname,
+ scan_report, is_cache);
+ add_scan_cache = false;
+ break;
+ default:
+ DEBUG(0, ("Scan result: Unknown result code %d: %s/%s: %s\n",
+ scan_result, vfs_h->conn->connectpath, fname,
+ scan_report));
+ virusfilter_treat_scan_error(vfs_h, virusfilter_h, smb_fname,
+ scan_report, is_cache);
+ add_scan_cache = false;
+ break;
+ }
+
+ if (virusfilter_h->cache_h) {
+ if (!is_cache && add_scan_cache) {
+ DEBUG(10, ("Adding new cache entry: %s, %d\n",
+ fname, scan_result));
+ if (!virusfilter_cache_entry_add(
+ virusfilter_h->cache_h, fname, scan_result,
+ scan_report))
+ {
+ DEBUG(0,("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(
+ vfs_handle_struct *vfs_h,
+ struct smb_filename *smb_fname,
+ files_struct *fsp,
+ int flags,
+ mode_t mode)
+{
+ TALLOC_CTX *mem_ctx = talloc_stackframe();
+ virusfilter_handle *virusfilter_h;
+ virusfilter_result scan_result;
+ char *fname = smb_fname->base_name;
+ char *dir_name = NULL;
+ const char *base_name = NULL;
+ int scan_errno = 0;
+ int test_prefix;
+ int test_suffix;
+ int rename_trap_count = 0;
+
+ SMB_VFS_HANDLE_GET_DATA(vfs_h, virusfilter_h, virusfilter_handle,
+ return -1);
+
+ test_prefix = strlen(virusfilter_h->rename_prefix);
+ test_suffix = strlen(virusfilter_h->rename_suffix);
+ if (test_prefix) {
+ rename_trap_count++;
+ }
+ if (test_suffix) {
+ rename_trap_count++;
+ }
+
+ if (!virusfilter_h->scan_on_open) {
+ DEBUG(5, ("Not scanned: scan on open is disabled: %s/%s\n",
+ vfs_h->conn->connectpath, fname));
+ goto virusfilter_vfs_open_next;
+ }
+
+ if (flags & O_TRUNC) {
+ DEBUG(5, ("Not scanned: Open flags have O_TRUNC: %s/%s\n",
+ vfs_h->conn->connectpath, fname));
+ goto virusfilter_vfs_open_next;
+ }
+
+ if (SMB_VFS_NEXT_STAT(vfs_h, smb_fname) != 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;
+ }
+ if (!S_ISREG(smb_fname->st.st_ex_mode)) {
+ DEBUG(5, ("Not scanned: Directory or special file: %s/%s\n",
+ vfs_h->conn->connectpath, fname));
+ goto virusfilter_vfs_open_next;
+ }
+ if (virusfilter_h->max_file_size > 0 && smb_fname->st.st_ex_size >
+ virusfilter_h->max_file_size)
+ {
+ DEBUG(5, ("Not scanned: file size > max file size: %s/%s\n",
+ vfs_h->conn->connectpath, fname));
+ goto virusfilter_vfs_open_next;
+ }
+ if (virusfilter_h->min_file_size > 0 && smb_fname->st.st_ex_size <
+ virusfilter_h->min_file_size)
+ {
+ DEBUG(5, ("Not scanned: file size < min file size: %s/%s\n",
+ vfs_h->conn->connectpath, fname));
+ goto virusfilter_vfs_open_next;
+ }
+
+ if (virusfilter_h->exclude_files && is_in_path(fname,
+ virusfilter_h->exclude_files, false))
+ {
+ DEBUG(5, ("Not scanned: exclude files: %s/%s\n",
+ vfs_h->conn->connectpath, fname));
+ goto virusfilter_vfs_open_next;
+ }
+
+ if (test_prefix || test_suffix) {
+ if (parent_dirname(mem_ctx, smb_fname->base_name, &dir_name,
+ &base_name))
+ {
+ if (test_prefix) {
+ if (strncmp(base_name,
+ virusfilter_h->rename_prefix,
+ test_prefix) != 0)
+ {
+ test_prefix = 0;
+ }
+ }
+ if (test_suffix) {
+ if (strcmp(base_name + (strlen(base_name) -
+ test_suffix),
+ virusfilter_h->rename_suffix) != 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 = virusfilter_h->infected_open_errno;
+ goto virusfilter_vfs_open_fail;
+ }
+ }
+ }
+
+ scan_result = virusfilter_scan(vfs_h, virusfilter_h, smb_fname);
+
+ switch (scan_result) {
+ case VIRUSFILTER_RESULT_CLEAN:
+ break;
+ case VIRUSFILTER_RESULT_INFECTED:
+ scan_errno = virusfilter_h->infected_open_errno;
+ goto virusfilter_vfs_open_fail;
+ case VIRUSFILTER_RESULT_ERROR:
+ if (virusfilter_h->block_access_on_error) {
+ DEBUG(5, ("Block access\n"));
+ scan_errno = virusfilter_h->scan_error_open_errno;
+ goto virusfilter_vfs_open_fail;
+ }
+ break;
+ default:
+ scan_errno = virusfilter_h->scan_error_open_errno;
+ goto virusfilter_vfs_open_fail;
+ }
+
+virusfilter_vfs_open_next:
+ TALLOC_FREE(mem_ctx);
+ return SMB_VFS_NEXT_OPEN(vfs_h, smb_fname, fsp, flags, mode);
+
+virusfilter_vfs_open_fail:
+ TALLOC_FREE(mem_ctx);
+ errno = (scan_errno != 0) ? scan_errno : EACCES;
+ return -1;
+}
+
+static int virusfilter_vfs_close(vfs_handle_struct *vfs_h, files_struct *fsp)
+{
+ TALLOC_CTX *mem_ctx = talloc_stackframe();
+ connection_struct *conn = vfs_h->conn;
+ virusfilter_handle *virusfilter_h;
+ char *fname = fsp->fsp_name->base_name;
+ int close_result, close_errno;
+ virusfilter_result scan_result;
+ int scan_errno = 0;
+
+ SMB_VFS_HANDLE_GET_DATA(vfs_h, virusfilter_h, virusfilter_handle,
+ 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(vfs_h, fsp);
+ 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) {
+ DEBUG(10, ("Removing cache entry (if existant): "
+ "fname: %s\n", fname));
+ virusfilter_cache_remove(virusfilter_h->cache_h,
+ fname);
+ }
+ goto virusfilter_vfs_close_fail;
+ }
+
+ if (fsp->is_directory) {
+ DEBUG(5, ("Not scanned: Directory: %s/%s\n",
+ conn->connectpath, fname));
+ TALLOC_FREE(mem_ctx);
+ return close_result;
+ }
+
+ if (!virusfilter_h->scan_on_close) {
+ if (virusfilter_h->scan_on_open && fsp->modified) {
+ if (virusfilter_h->cache_h) {
+ DEBUG(10, ("Removing cache entry (if "
+ "existant): fname: %s\n", fname));
+ virusfilter_cache_remove(
+ virusfilter_h->cache_h, fname);
+ }
+ }
+ DEBUG(5, ("Not scanned: scan on close is disabled: %s/%s\n",
+ conn->connectpath, fname));
+ TALLOC_FREE(mem_ctx);
+ return close_result;
+ }
+
+ if (!fsp->modified) {
+ DEBUG(3, ("Not scanned: File not modified: %s/%s\n",
+ conn->connectpath, fname));
+
+ TALLOC_FREE(mem_ctx);
+ return close_result;
+ }
+
+ if (virusfilter_h->exclude_files && is_in_path(fname,
+ virusfilter_h->exclude_files, false))
+ {
+ DEBUG(5, ("Not scanned: exclude files: %s/%s\n",
+ conn->connectpath, fname));
+ TALLOC_FREE(mem_ctx);
+ return close_result;
+ }
+
+ scan_result = virusfilter_scan(vfs_h, virusfilter_h, fsp->fsp_name);
+
+ switch (scan_result) {
+ case VIRUSFILTER_RESULT_CLEAN:
+ break;
+ case VIRUSFILTER_RESULT_INFECTED:
+ scan_errno = virusfilter_h->infected_close_errno;
+ goto virusfilter_vfs_close_fail;
+ case VIRUSFILTER_RESULT_ERROR:
+ if (virusfilter_h->block_access_on_error) {
+ DEBUG(5, ("Block access\n"));
+ scan_errno = virusfilter_h->scan_error_close_errno;
+ goto virusfilter_vfs_close_fail;
+ }
+ break;
+ default:
+ scan_errno = virusfilter_h->scan_error_close_errno;
+ goto virusfilter_vfs_close_fail;
+ }
+
+ TALLOC_FREE(mem_ctx);
+ errno = close_errno;
+
+ return close_result;
+
+virusfilter_vfs_close_fail:
+
+ TALLOC_FREE(mem_ctx);
+ errno = (scan_errno != 0) ? scan_errno : close_errno;
+
+ return close_result;
+}
+
+static int virusfilter_vfs_unlink(
+ vfs_handle_struct *vfs_h,
+ const struct smb_filename *smb_fname)
+{
+ int ret = SMB_VFS_NEXT_UNLINK(vfs_h, smb_fname);
+ virusfilter_handle *virusfilter_h;
+ char *fname;
+
+ if (ret != 0 && errno != ENOENT) {
+ return ret;
+ }
+
+ SMB_VFS_HANDLE_GET_DATA(vfs_h, virusfilter_h, virusfilter_handle,
+ return -1);
+
+ if (virusfilter_h->cache_h) {
+ fname = smb_fname->base_name;
+ DEBUG(10, ("Removing cache entry (if existant): fname: %s\n",
+ fname));
+ virusfilter_cache_remove(virusfilter_h->cache_h, fname);
+ }
+
+ return ret;
+}
+
+static int virusfilter_vfs_rename(
+ vfs_handle_struct *vfs_h,
+ const struct smb_filename *smb_fname_src,
+ const struct smb_filename *smb_fname_dst)
+{
+ int ret = SMB_VFS_NEXT_RENAME(vfs_h, smb_fname_src, smb_fname_dst);
+ virusfilter_handle *virusfilter_h;
+ char *fname;
+
+ if (ret != 0) {
+ return ret;
+ }
+
+ SMB_VFS_HANDLE_GET_DATA(vfs_h, virusfilter_h, virusfilter_handle,
+ return -1);
+
+ if (virusfilter_h->cache_h) {
+ fname = smb_fname_dst->base_name;
+ DEBUG(10, ("Removing cache entry (if existant): fname: "
+ "%s\n", fname));
+ virusfilter_cache_remove(virusfilter_h->cache_h, fname);
+
+ fname = smb_fname_src->base_name;
+ DEBUG(10, ("Renaming cache entry: fname: %s to: %s\n", fname,
+ smb_fname_dst->base_name));
+ virusfilter_cache_entry_rename(virusfilter_h->cache_h, fname,
+ smb_fname_dst->base_name);
+ }
+
+ return ret;
+}
+
+/* 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,
+};
+
+#define MAKE_FN_NAME(x) NTSTATUS vfs_virusfilter_ ## x ## _init(void)
+#define VFS_VIRUSFILTER_INIT(ENGINE) MAKE_FN_NAME(ENGINE)
+
+VFS_VIRUSFILTER_INIT(VIRUSFILTER_ENGINE);
+VFS_VIRUSFILTER_INIT(VIRUSFILTER_ENGINE)
+{
+ NTSTATUS ret;
+
+ ret = smb_register_vfs(SMB_VFS_INTERFACE_VERSION,
+ VIRUSFILTER_MODULE_NAME, &vfs_virusfilter_fns);
+ if (!NT_STATUS_IS_OK(ret)) {
+ return ret;
+ }
+
+ virusfilter_debug_level = debug_add_class(VIRUSFILTER_MODULE_NAME);
+ if (virusfilter_debug_level == -1) {
+ virusfilter_debug_level = DBGC_VFS;
+ DEBUG(0, ("Couldn't register custom debugging class!\n"));
+ } else {
+ DEBUG(10, ("Debug class number of '%s': %d\n",
+ VIRUSFILTER_MODULE_NAME, virusfilter_debug_level));
+ }
+
+ DEBUG(5,("%s registered\n", VIRUSFILTER_MODULE_NAME));
+
+ return ret;
+}
+
+#endif /* _VIRUSFILTER_VFS_H */
+
diff --git a/source3/modules/wscript_build b/source3/modules/wscript_build
index a5d8407..888478e 100644
--- a/source3/modules/wscript_build
+++ b/source3/modules/wscript_build
@@ -14,6 +14,11 @@ bld.SAMBA3_LIBRARY('non_posix_acls',
deps='samba-util vfs',
private_library=True)
+bld.SAMBA3_SUBSYSTEM('VFS_VIRUSFILTER_UTILS',
+ source='vfs_virusfilter_utils.c',
+ deps='strv',
+ enabled=(bld.SAMBA3_IS_ENABLED_MODULE('vfs_virusfilter_clamav') or bld.SAMBA3_IS_ENABLED_MODULE('vfs_virusfilter_sophos') or bld.SAMBA3_IS_ENABLED_MODULE('vfs_virusfilter_fsav')))
+
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')))
@@ -482,6 +487,30 @@ 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_clamav',
+ subsystem='vfs',
+ source='vfs_virusfilter_clamav.c',
+ deps='samba-util VFS_VIRUSFILTER_UTILS',
+ init_function='',
+ internal_module=bld.SAMBA3_IS_STATIC_MODULE('vfs_virusfilter_clamav'),
+ enabled=bld.SAMBA3_IS_ENABLED_MODULE('vfs_virusfilter_clamav'))
+
+bld.SAMBA3_MODULE('vfs_virusfilter_fsav',
+ subsystem='vfs',
+ source='vfs_virusfilter_fsav.c',
+ deps='samba-util VFS_VIRUSFILTER_UTILS',
+ init_function='',
+ internal_module=bld.SAMBA3_IS_STATIC_MODULE('vfs_virusfilter_fsav'),
+ enabled=bld.SAMBA3_IS_ENABLED_MODULE('vfs_virusfilter_fsav'))
+
+bld.SAMBA3_MODULE('vfs_virusfilter_sophos',
+ subsystem='vfs',
+ source='vfs_virusfilter_sophos.c',
+ deps='samba-util VFS_VIRUSFILTER_UTILS',
+ init_function='',
+ internal_module=bld.SAMBA3_IS_STATIC_MODULE('vfs_virusfilter_sophos'),
+ enabled=bld.SAMBA3_IS_ENABLED_MODULE('vfs_virusfilter_sophos'))
+
bld.SAMBA3_MODULE('vfs_vxfs',
subsystem='vfs',
source='lib_vxfs.c vfs_vxfs.c',
diff --git a/source3/wscript b/source3/wscript
index 5ce1b77..155391e 100644
--- a/source3/wscript
+++ b/source3/wscript
@@ -1674,7 +1674,8 @@ 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_clamav
+ vfs_virusfilter_fsav vfs_virusfilter_sophos
'''))
default_shared_modules.extend(TO_LIST('auth_script idmap_tdb2 idmap_script'))
# these have broken dependencies
@@ -1859,6 +1860,17 @@ main() {
Logs.info("%s: %s" % (shared_env, ','.join(conf.env[shared_env])))
conf.SAMBA_CONFIG_H('include/config.h')
+ old_defines = conf.env['defines']
+ conf.env['defines'] = {}
+ conf.define('VARDIR', conf.env['STATEDIR'])
+ sambaversion = samba_version.load_version(env=None)
+ conf.define('SAMBA_VERSION_NUMBER', int('%d%02d%02d' % (
+ sambaversion.MAJOR,
+ sambaversion.MINOR,
+ sambaversion.RELEASE,
+ )))
+ conf.write_config_header('include/vfs_virusfilter_config.h')
+ conf.env['defines'] = old_defines
def ctags(ctx):
"build 'tags' file using ctags"