Samba-VirusFilter: F-Secure AntiVirus (fsav) VFS and man page.
authorTrever L. Adams <trever.adams@gmail.com>
Tue, 18 Oct 2016 19:39:20 +0000 (13:39 -0600)
committerRalph Boehme <slow@samba.org>
Wed, 24 Jan 2018 09:29:46 +0000 (10:29 +0100)
Signed-off-by: Trever L. Adams <trever.adams@gmail.com>
Signed-off-by: SATOH Fumiyasu <fumiyas@osstech.co.jp>
Reviewed-by: Jeremy Allison <jra@samba.org>
Reviewed-by: Ralph Boehme <slow@samba.org>
docs-xml/manpages/vfs_virusfilter.8.xml
source3/modules/vfs_virusfilter.c
source3/modules/vfs_virusfilter_common.h
source3/modules/vfs_virusfilter_fsav.c [new file with mode: 0644]
source3/modules/wscript_build

index c4bc892004316aa23f10cf63fccba41074f4c29e..2e70ab0b55356d107fee4d4ff5b6a319b5b51642 100644 (file)
@@ -44,6 +44,8 @@
                <itemizedlist>
                  <listitem><para><emphasis>sophos</emphasis>, the Sophos AV
                  scanner</para></listitem>
+                 <listitem><para><emphasis>fsav</emphasis>, the F-Secure AV
+                 scanner</para></listitem>
                </itemizedlist>
                </listitem>
                </varlistentry>
@@ -58,6 +60,8 @@
                </para>
                <para>For the <emphasis>sophos</emphasis>backend the default is
                <emphasis>/var/run/savdi/sssp.sock</emphasis>.</para>
+               <para>For the <emphasis>fsav</emphasis> backend the default is
+               <emphasis>/tmp/.fsav-0</emphasis>.</para>
                </listitem>
                </varlistentry>
 
                <term>virusfilter:scan archive = true</term>
                <listitem>
                <para>This defines whether or not to scan archives.</para>
-               <para>Sophos supports this and defaults to false.</para>
+               <para>Sophos and F-Secure support this and it defaults to false.</para>
                </listitem>
                </varlistentry>
 
                <term>virusfilter:max nested scan archive = 1</term>
                <listitem>
                <para>This defines the maximum depth to search nested archives.</para>
-               <para>The Sophos module supports this and defaults to 1.</para>
+               <para>The Sophos and F-Secure support this and it defaults to 1.</para>
+               </listitem>
+               </varlistentry>
+
+               <varlistentry>
+               <term>virusfilter:scan mime = true</term>
+               <listitem>
+               <para>This defines whether or not to scan mime files.</para>
+               <para>Only the <emphasis>fsav</emphasis>scanner supports this
+               option and defaults to false.</para>
                </listitem>
                </varlistentry>
 
                </listitem>
                </varlistentry>
 
+               <varlistentry>
+               <term>virusfilter:block suspected file = false</term>
+               <listitem>
+               <para>With this option on, suspected malware will be blocked as
+               well. Only the <emphasis>fsav</emphasis>scanner supports this
+               option.</para>
+               <para>If this option is not set, the default is false.</para>
+               </listitem>
+               </varlistentry>
+
        </variablelist>
 </refsect1>
 
index 8947e35b14ba8179b3a80a642c434b5414277184..338b4fc899c4c9911fd911597f81a7e3acc2da31 100644 (file)
@@ -445,6 +445,9 @@ static int virusfilter_vfs_connect(
        case VIRUSFILTER_SCANNER_SOPHOS:
                ret = virusfilter_sophos_init(config);
                break;
+       case VIRUSFILTER_SCANNER_FSAV:
+               ret = virusfilter_fsav_init(config);
+               break;
        default:
                DBG_ERR("Unhandled scanner %d\n", backend);
                return -1;
index 69519c9daa4510e9b80d8eb6b017b0ac418685b8..a28ce2978fa94abf1d8de5505e59eb667856a4be 100644 (file)
@@ -147,5 +147,6 @@ struct virusfilter_backend {
 };
 
 int virusfilter_sophos_init(struct virusfilter_config *config);
+int virusfilter_fsav_init(struct virusfilter_config *config);
 
 #endif /* _VIRUSFILTER_COMMON_H */
diff --git a/source3/modules/vfs_virusfilter_fsav.c b/source3/modules/vfs_virusfilter_fsav.c
new file mode 100644 (file)
index 0000000..2b874d7
--- /dev/null
@@ -0,0 +1,451 @@
+/*
+   Samba-VirusFilter VFS modules
+   F-Secure Anti-Virus fsavd support
+   Copyright (C) 2010-2016 SATOH Fumiyasu @ OSS Technology Corp., Japan
+
+   This program is free software; you can redistribute it and/or modify
+   it under the terms of the GNU General Public License as published by
+   the Free Software Foundation; either version 3 of the License, or
+   (at your option) any later version.
+
+   This program is distributed in the hope that it will be useful,
+   but WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+   GNU General Public License for more details.
+
+   You should have received a copy of the GNU General Public License
+   along with this program.  If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#include "vfs_virusfilter_common.h"
+#include "vfs_virusfilter_utils.h"
+
+#ifdef FSAV_DEFAULT_SOCKET_PATH
+#  define VIRUSFILTER_DEFAULT_SOCKET_PATH      FSAV_DEFAULT_SOCKET_PATH
+#else
+#  define VIRUSFILTER_DEFAULT_SOCKET_PATH      "/tmp/.fsav-0"
+#endif
+
+/* Default values for module-specific configuration variables */
+/* 5 = F-Secure Linux 7 or later? */
+
+#define VIRUSFILTER_DEFAULT_FSAV_PROTOCOL              5
+#define VIRUSFILTER_DEFAULT_SCAN_RISKWARE              false
+#define VIRUSFILTER_DEFAULT_STOP_SCAN_ON_FIRST         true
+#define VIRUSFILTER_DEFAULT_FILTER_FILENAME            false
+
+struct virusfilter_fsav_config {
+       /* Backpointer */
+       struct virusfilter_config *config;
+
+       int fsav_protocol;
+       bool scan_riskware;
+       bool stop_scan_on_first;
+       bool filter_filename;
+};
+
+static void virusfilter_fsav_scan_end(struct virusfilter_config *config);
+
+static int virusfilter_fsav_destruct_config(
+       struct virusfilter_fsav_config *fsav_config)
+{
+       virusfilter_fsav_scan_end(fsav_config->config);
+       return 0;
+}
+
+static int virusfilter_fsav_connect(
+       struct vfs_handle_struct *handle,
+       struct virusfilter_config *config,
+       const char *svc,
+       const char *user)
+{
+       int snum = SNUM(handle->conn);
+       struct virusfilter_fsav_config *fsav_config = NULL;
+
+       fsav_config = talloc_zero(config->backend,
+                                 struct virusfilter_fsav_config);
+       if (fsav_config == NULL) {
+               return -1;
+       }
+
+       fsav_config->config = config;
+
+       fsav_config->fsav_protocol = lp_parm_int(
+               snum, "virusfilter", "fsav protocol",
+               VIRUSFILTER_DEFAULT_FSAV_PROTOCOL);
+
+       fsav_config->scan_riskware = lp_parm_bool(
+               snum, "virusfilter", "scan riskware",
+               VIRUSFILTER_DEFAULT_SCAN_RISKWARE);
+
+       fsav_config->stop_scan_on_first = lp_parm_bool(
+               snum, "virusfilter", "stop scan on first",
+               VIRUSFILTER_DEFAULT_STOP_SCAN_ON_FIRST);
+
+       fsav_config->filter_filename = lp_parm_bool(
+               snum, "virusfilter", "filter filename",
+               VIRUSFILTER_DEFAULT_FILTER_FILENAME);
+
+       talloc_set_destructor(fsav_config, virusfilter_fsav_destruct_config);
+
+       config->backend->backend_private = fsav_config;
+
+       config->block_suspected_file = lp_parm_bool(
+               snum, "virusfilter", "block suspected file", false);
+
+       return 0;
+}
+
+static virusfilter_result virusfilter_fsav_scan_init(
+       struct virusfilter_config *config)
+{
+       struct virusfilter_fsav_config *fsav_config = NULL;
+       struct virusfilter_io_handle *io_h = config->io_h;
+       char *reply = NULL;
+       bool ok;
+       int ret;
+
+       fsav_config = talloc_get_type_abort(config->backend->backend_private,
+                                           struct virusfilter_fsav_config);
+
+       if (io_h->stream != NULL) {
+               DBG_DEBUG("fsavd: Checking if connection is alive\n");
+
+               /* FIXME: I don't know the correct PING command format... */
+               ok = virusfilter_io_writefl_readl(io_h, &reply, "PING");
+               if (ok) {
+                       ret = strncmp(reply, "ERROR\t", 6);
+                       if (ret == 0) {
+                               DBG_DEBUG("fsavd: Re-using existent "
+                                         "connection\n");
+                               goto virusfilter_fsav_init_succeed;
+                       }
+               }
+
+               DBG_DEBUG("fsavd: Closing dead connection\n");
+               virusfilter_fsav_scan_end(config);
+       }
+
+       DBG_INFO("fsavd: Connecting to socket: %s\n",
+                config->socket_path);
+
+       become_root();
+       ok = virusfilter_io_connect_path(io_h, config->socket_path);
+       unbecome_root();
+
+       if (!ok) {
+               DBG_ERR("fsavd: Connecting to socket failed: %s: %s\n",
+                       config->socket_path, strerror(errno));
+               goto virusfilter_fsav_init_failed;
+       }
+
+       TALLOC_FREE(reply);
+
+       ok = virusfilter_io_readl(talloc_tos(), io_h, &reply);
+       if (!ok) {
+               DBG_ERR("fsavd: Reading greeting message failed: %s\n",
+                       strerror(errno));
+               goto virusfilter_fsav_init_failed;
+       }
+       ret = strncmp(reply, "DBVERSION\t", 10);
+       if (ret != 0) {
+               DBG_ERR("fsavd: Invalid greeting message: %s\n",
+                       reply);
+               goto virusfilter_fsav_init_failed;
+       }
+
+       DBG_DEBUG("fsavd: Connected\n");
+
+       DBG_INFO("fsavd: Configuring\n");
+
+       TALLOC_FREE(reply);
+
+       ok = virusfilter_io_writefl_readl(io_h, &reply, "PROTOCOL\t%d",
+                                         fsav_config->fsav_protocol);
+       if (!ok) {
+               DBG_ERR("fsavd: PROTOCOL: I/O error: %s\n", strerror(errno));
+               goto virusfilter_fsav_init_failed;
+       }
+       ret = strncmp(reply, "OK\t", 3);
+       if (ret != 0) {
+               DBG_ERR("fsavd: PROTOCOL: Not accepted: %s\n",
+                       reply);
+               goto virusfilter_fsav_init_failed;
+       }
+
+       TALLOC_FREE(reply);
+
+       ok = virusfilter_io_writefl_readl(io_h, &reply,
+                                         "CONFIGURE\tSTOPONFIRST\t%d",
+                                         fsav_config->stop_scan_on_first ?
+                                         1 : 0);
+       if (!ok) {
+               DBG_ERR("fsavd: CONFIGURE STOPONFIRST: I/O error: %s\n",
+                       strerror(errno));
+               goto virusfilter_fsav_init_failed;
+       }
+       ret = strncmp(reply, "OK\t", 3);
+       if (ret != 0) {
+               DBG_ERR("fsavd: CONFIGURE STOPONFIRST: Not accepted: %s\n",
+                       reply);
+               goto virusfilter_fsav_init_failed;
+       }
+
+       TALLOC_FREE(reply);
+
+       ok = virusfilter_io_writefl_readl(io_h, &reply, "CONFIGURE\tFILTER\t%d",
+                                         fsav_config->filter_filename ? 1 : 0);
+       if (!ok) {
+               DBG_ERR("fsavd: CONFIGURE FILTER: I/O error: %s\n",
+                       strerror(errno));
+               goto virusfilter_fsav_init_failed;
+       }
+       ret = strncmp(reply, "OK\t", 3);
+       if (ret != 0) {
+               DBG_ERR("fsavd: CONFIGURE FILTER: Not accepted: %s\n",
+                       reply);
+               goto virusfilter_fsav_init_failed;
+       }
+
+       TALLOC_FREE(reply);
+
+       ok = virusfilter_io_writefl_readl(io_h, &reply,
+                                         "CONFIGURE\tARCHIVE\t%d",
+                                         config->scan_archive ? 1 : 0);
+       if (!ok) {
+               DBG_ERR("fsavd: CONFIGURE ARCHIVE: I/O error: %s\n",
+                       strerror(errno));
+               goto virusfilter_fsav_init_failed;
+       }
+       ret = strncmp(reply, "OK\t", 3);
+       if (ret != 0) {
+               DBG_ERR("fsavd: CONFIGURE ARCHIVE: Not accepted: %s\n",
+                       reply);
+               goto virusfilter_fsav_init_failed;
+       }
+
+       TALLOC_FREE(reply);
+
+       ok = virusfilter_io_writefl_readl(io_h, &reply,
+                                         "CONFIGURE\tMAXARCH\t%d",
+                                         config->max_nested_scan_archive);
+       if (!ok) {
+               DBG_ERR("fsavd: CONFIGURE MAXARCH: I/O error: %s\n",
+                       strerror(errno));
+               goto virusfilter_fsav_init_failed;
+       }
+       ret = strncmp(reply, "OK\t", 3);
+       if (ret != 0) {
+               DBG_ERR("fsavd: CONFIGURE MAXARCH: Not accepted: %s\n",
+                       reply);
+               goto virusfilter_fsav_init_failed;
+       }
+
+       TALLOC_FREE(reply);
+
+       ok = virusfilter_io_writefl_readl(io_h, &reply,
+                                         "CONFIGURE\tMIME\t%d",
+                                         config->scan_mime ? 1 : 0);
+       if (!ok) {
+               DBG_ERR("fsavd: CONFIGURE MIME: I/O error: %s\n",
+                       strerror(errno));
+               goto virusfilter_fsav_init_failed;
+       }
+       ret = strncmp(reply, "OK\t", 3);
+       if (ret != 0) {
+               DBG_ERR("fsavd: CONFIGURE MIME: Not accepted: %s\n",
+                       reply);
+               goto virusfilter_fsav_init_failed;
+       }
+
+       TALLOC_FREE(reply);
+
+       ok = virusfilter_io_writefl_readl(io_h, &reply, "CONFIGURE\tRISKWARE\t%d",
+                                         fsav_config->scan_riskware ? 1 : 0);
+       if (!ok) {
+               DBG_ERR("fsavd: CONFIGURE RISKWARE: I/O error: %s\n",
+                       strerror(errno));
+               goto virusfilter_fsav_init_failed;
+       }
+       ret = strncmp(reply, "OK\t", 3);
+       if (ret != 0) {
+               DBG_ERR("fsavd: CONFIGURE RISKWARE: Not accepted: %s\n",
+                       reply);
+               goto virusfilter_fsav_init_failed;
+       }
+
+       DBG_DEBUG("fsavd: Configured\n");
+
+virusfilter_fsav_init_succeed:
+       TALLOC_FREE(reply);
+       return VIRUSFILTER_RESULT_OK;
+
+virusfilter_fsav_init_failed:
+       TALLOC_FREE(reply);
+       virusfilter_fsav_scan_end(config);
+
+       return VIRUSFILTER_RESULT_ERROR;
+}
+
+static void virusfilter_fsav_scan_end(struct virusfilter_config *config)
+{
+       struct virusfilter_io_handle *io_h = config->io_h;
+
+       DBG_INFO("fsavd: Disconnecting\n");
+       virusfilter_io_disconnect(io_h);
+}
+
+static virusfilter_result virusfilter_fsav_scan(
+       struct vfs_handle_struct *handle,
+       struct virusfilter_config *config,
+       const struct files_struct *fsp,
+       char **reportp)
+{
+       char *cwd_fname = fsp->conn->cwd_fname->base_name;
+       const char *fname = fsp->fsp_name->base_name;
+       struct virusfilter_io_handle *io_h = config->io_h;
+       virusfilter_result result = VIRUSFILTER_RESULT_CLEAN;
+       char *report = NULL;
+       char *reply = NULL;
+       char *reply_token, *reply_saveptr;
+       bool ok;
+
+       DBG_INFO("Scanning file: %s/%s\n", cwd_fname, fname);
+
+       ok = virusfilter_io_writevl(io_h, "SCAN\t", 5, cwd_fname,
+                                   (int)strlen(cwd_fname), "/", 1, fname,
+                                   (int)strlen(fname), NULL);
+       if (!ok) {
+               DBG_ERR("fsavd: SCAN: Write error: %s\n", strerror(errno));
+               result = VIRUSFILTER_RESULT_ERROR;
+               report = talloc_asprintf(talloc_tos(),
+                                        "Scanner I/O error: %s\n",
+                                        strerror(errno));
+               goto virusfilter_fsav_scan_return;
+       }
+
+       TALLOC_FREE(reply);
+
+       for (;;) {
+               if (virusfilter_io_readl(talloc_tos(), io_h, &reply) != true) {
+                       DBG_ERR("fsavd: SCANFILE: Read error: %s\n",
+                               strerror(errno));
+                       result = VIRUSFILTER_RESULT_ERROR;
+                       report = talloc_asprintf(talloc_tos(),
+                                                "Scanner I/O error: %s\n",
+                                                strerror(errno));
+                       break;
+               }
+
+               reply_token = strtok_r(reply, "\t", &reply_saveptr);
+
+               if (strcmp(reply_token, "OK") == 0) {
+                       break;
+               } else if (strcmp(reply_token, "CLEAN") == 0) {
+
+                       /* CLEAN\t<FILEPATH> */
+                       result = VIRUSFILTER_RESULT_CLEAN;
+                       report = talloc_asprintf(talloc_tos(), "Clean");
+               } else if (strcmp(reply_token, "INFECTED") == 0 ||
+                          strcmp(reply_token, "ARCHIVE_INFECTED") == 0 ||
+                          strcmp(reply_token, "MIME_INFECTED") == 0 ||
+                          strcmp(reply_token, "RISKWARE") == 0 ||
+                          strcmp(reply_token, "ARCHIVE_RISKWARE") == 0 ||
+                          strcmp(reply_token, "MIME_RISKWARE") == 0)
+               {
+
+                       /* INFECTED\t<FILEPATH>\t<REPORT>\t<ENGINE> */
+                       result = VIRUSFILTER_RESULT_INFECTED;
+                       reply_token = strtok_r(NULL, "\t", &reply_saveptr);
+                       reply_token = strtok_r(NULL, "\t", &reply_saveptr);
+                       if (reply_token != NULL) {
+                                 report = talloc_strdup(talloc_tos(),
+                                                        reply_token);
+                       } else {
+                                 report = talloc_asprintf(talloc_tos(),
+                                                       "UNKNOWN INFECTION");
+                       }
+               } else if (strcmp(reply_token, "OPEN_ARCHIVE") == 0) {
+
+                       /* Ignore */
+               } else if (strcmp(reply_token, "CLOSE_ARCHIVE") == 0) {
+
+                       /* Ignore */
+               } else if ((strcmp(reply_token, "SUSPECTED") == 0 ||
+                          strcmp(reply_token, "ARCHIVE_SUSPECTED") == 0 ||
+                          strcmp(reply_token, "MIME_SUSPECTED") == 0) &&
+                          config->block_suspected_file)
+               {
+                       result = VIRUSFILTER_RESULT_SUSPECTED;
+                       reply_token = strtok_r(NULL, "\t", &reply_saveptr);
+                       reply_token = strtok_r(NULL, "\t", &reply_saveptr);
+                       if (reply_token != NULL) {
+                                 report = talloc_strdup(talloc_tos(),
+                                                        reply_token);
+                       } else {
+                                 report = talloc_asprintf(talloc_tos(),
+                                               "UNKNOWN REASON SUSPECTED");
+                       }
+               } else if (strcmp(reply_token, "SCAN_FAILURE") == 0) {
+
+                       /* SCAN_FAILURE\t<FILEPATH>\t0x<CODE>\t<REPORT> [<ENGINE>] */
+                       result = VIRUSFILTER_RESULT_ERROR;
+                       reply_token = strtok_r(NULL, "\t", &reply_saveptr);
+                       reply_token = strtok_r(NULL, "\t", &reply_saveptr);
+                       DBG_ERR("fsavd: SCANFILE: Scaner error: %s\n",
+                               reply_token ? reply_token : "UNKNOWN ERROR");
+                       report = talloc_asprintf(talloc_tos(),
+                                                "Scanner error: %s",
+                                                reply_token ? reply_token :
+                                                "UNKNOWN ERROR");
+               } else {
+                       result = VIRUSFILTER_RESULT_ERROR;
+                       DBG_ERR("fsavd: SCANFILE: Invalid reply: %s\t",
+                               reply_token);
+                       report = talloc_asprintf(talloc_tos(),
+                                                "Scanner communication error");
+               }
+
+               TALLOC_FREE(reply);
+       }
+
+virusfilter_fsav_scan_return:
+       TALLOC_FREE(reply);
+
+       if (report == NULL) {
+               *reportp = talloc_asprintf(talloc_tos(), "Scanner report memory "
+                                          "error");
+       } else {
+               *reportp = report;
+       }
+
+       return result;
+}
+
+static struct virusfilter_backend_fns virusfilter_backend_fsav ={
+       .connect = virusfilter_fsav_connect,
+       .disconnect = NULL,
+       .scan_init = virusfilter_fsav_scan_init,
+       .scan = virusfilter_fsav_scan,
+       .scan_end = virusfilter_fsav_scan_end,
+};
+
+int virusfilter_fsav_init(struct virusfilter_config *config)
+{
+       struct virusfilter_backend *backend = NULL;
+
+       if (config->socket_path == NULL) {
+               config->socket_path = VIRUSFILTER_DEFAULT_SOCKET_PATH;
+       }
+
+       backend = talloc_zero(config, struct virusfilter_backend);
+       if (backend == NULL) {
+               return -1;
+       }
+
+       backend->fns = &virusfilter_backend_fsav;
+       backend->name = "fsav";
+
+       config->backend = backend;
+       return 0;
+}
index 14fddb3b30eb023941d642f556fb07ebdc90c02c..f63c00a9955901f7978a102e4094cc63f5eb2dc9 100644 (file)
@@ -515,6 +515,7 @@ bld.SAMBA3_MODULE('vfs_virusfilter',
                  source='''
                  vfs_virusfilter.c
                  vfs_virusfilter_sophos.c
+                 vfs_virusfilter_fsav.c
                  ''',
                  deps='samba-util VFS_VIRUSFILTER_UTILS',
                  init_function='',