Samba-VirusFilter: Sophos VFS backend.
authorTrever L. Adams <trever.adams@gmail.com>
Tue, 18 Oct 2016 19:38:14 +0000 (13:38 -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_sophos.c [new file with mode: 0644]
source3/modules/wscript_build

index eb6112e..c4bc892 100644 (file)
                <term>virusfilter:scanner</term>
                <listitem>
                <para>The antivirus scan-engine.</para>
+               <itemizedlist>
+                 <listitem><para><emphasis>sophos</emphasis>, the Sophos AV
+                 scanner</para></listitem>
+               </itemizedlist>
                </listitem>
                </varlistentry>
 
@@ -52,6 +56,8 @@
                <para>If this option is not set, the default path depends on the
                configured AV scanning engine.
                </para>
+               <para>For the <emphasis>sophos</emphasis>backend the default is
+               <emphasis>/var/run/savdi/sssp.sock</emphasis>.</para>
                </listitem>
                </varlistentry>
 
index a23d1f7..8947e35 100644 (file)
@@ -441,10 +441,17 @@ static int virusfilter_vfs_connect(
                return -1;
        }
 
-       /* This goes away as soon as the next commit adds an actual backend... */
-       if (config->backend == NULL) {
-               DBG_INFO("Not implemented\n");
-               return SMB_VFS_NEXT_CONNECT(handle, svc, user);
+       switch (backend) {
+       case VIRUSFILTER_SCANNER_SOPHOS:
+               ret = virusfilter_sophos_init(config);
+               break;
+       default:
+               DBG_ERR("Unhandled scanner %d\n", backend);
+               return -1;
+       }
+       if (ret != 0) {
+               DBG_ERR("Scanner backend init failed\n");
+               return -1;
        }
 
        if (config->backend->fns->connect != NULL) {
index 468883f..69519c9 100644 (file)
@@ -146,4 +146,6 @@ struct virusfilter_backend {
        void *backend_private;
 };
 
+int virusfilter_sophos_init(struct virusfilter_config *config);
+
 #endif /* _VIRUSFILTER_COMMON_H */
diff --git a/source3/modules/vfs_virusfilter_sophos.c b/source3/modules/vfs_virusfilter_sophos.c
new file mode 100644 (file)
index 0000000..72051cd
--- /dev/null
@@ -0,0 +1,391 @@
+/*
+   Samba-VirusFilter VFS modules
+   Sophos Anti-Virus savdid (SSSP/1.0) support
+   Copyright (C) 2010-2016 SATOH Fumiyasu @ OSS Technology Corp., Japan
+
+   This program is free software; you can redistribute it and/or modify
+   it under the terms of the GNU General Public License as published by
+   the Free Software Foundation; either version 3 of the License, or
+   (at your option) any later version.
+
+   This program is distributed in the hope that it will be useful,
+   but WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+   GNU General Public License for more details.
+
+   You should have received a copy of the GNU General Public License
+   along with this program.  If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#include "vfs_virusfilter_common.h"
+#include "vfs_virusfilter_utils.h"
+
+/* Default values for standard "extra" configuration variables */
+#ifdef SOPHOS_DEFAULT_SOCKET_PATH
+#  define VIRUSFILTER_DEFAULT_SOCKET_PATH      SOPHOS_DEFAULT_SOCKET_PATH
+#else
+#  define VIRUSFILTER_DEFAULT_SOCKET_PATH      "/var/run/savdi/sssp.sock"
+#endif
+
+static void virusfilter_sophos_scan_end(struct virusfilter_config *config);
+
+/* Python's urllib.quote(string[, safe]) clone */
+static int virusfilter_url_quote(const char *src, char *dst, int dst_size)
+{
+       char *dst_c = dst;
+       static char hex[] = "0123456789ABCDEF";
+
+       for (; *src != '\0'; src++) {
+               if ((*src < '0' && *src != '-' && *src != '.' && *src != '/') ||
+                   (*src > '9' && *src < 'A') ||
+                   (*src > 'Z' && *src < 'a' && *src != '_') ||
+                   (*src > 'z'))
+               {
+                       if (dst_size < 4) {
+                               return -1;
+                       }
+                       *dst_c++ = '%';
+                       *dst_c++ = hex[(*src >> 4) & 0x0F];
+                       *dst_c++ = hex[*src & 0x0F];
+                       dst_size -= 3;
+               } else {
+                       if (dst_size < 2) {
+                               return -1;
+                       }
+                       *dst_c++ = *src;
+                       dst_size--;
+               }
+       }
+
+       *dst_c = '\0';
+
+       return (dst_c - dst);
+}
+
+static int virusfilter_sophos_connect(
+       struct vfs_handle_struct *handle,
+       struct virusfilter_config *config,
+       const char *svc,
+       const char *user)
+{
+       virusfilter_io_set_readl_eol(config->io_h, "\x0D\x0A", 2);
+
+       return 0;
+}
+
+static virusfilter_result virusfilter_sophos_scan_ping(
+       struct virusfilter_config *config)
+{
+       struct virusfilter_io_handle *io_h = config->io_h;
+       char *reply = NULL;
+       bool ok;
+       int ret;
+
+       /* SSSP/1.0 has no "PING" command */
+       ok = virusfilter_io_writel(io_h, "SSSP/1.0 OPTIONS\n", 17);
+       if (!ok) {
+               return VIRUSFILTER_RESULT_ERROR;
+       }
+
+       for (;;) {
+               ok = virusfilter_io_readl(talloc_tos(), io_h, &reply);
+               if (!ok) {
+                       return VIRUSFILTER_RESULT_ERROR;
+               }
+               ret = strcmp(reply, "");
+               if (ret == 0) {
+                       break;
+               }
+               TALLOC_FREE(reply);
+       }
+
+       TALLOC_FREE(reply);
+       return VIRUSFILTER_RESULT_OK;
+}
+
+static virusfilter_result virusfilter_sophos_scan_init(
+       struct virusfilter_config *config)
+{
+       struct virusfilter_io_handle *io_h = config->io_h;
+       char *reply = NULL;
+       int ret;
+       bool ok;
+
+       if (io_h->stream != NULL) {
+               DBG_DEBUG("SSSP: Checking if connection is alive\n");
+
+               ret = virusfilter_sophos_scan_ping(config);
+               if (ret == VIRUSFILTER_RESULT_OK)
+               {
+                       DBG_DEBUG("SSSP: Re-using existent connection\n");
+                       return VIRUSFILTER_RESULT_OK;
+               }
+
+               DBG_INFO("SSSP: Closing dead connection\n");
+               virusfilter_sophos_scan_end(config);
+       }
+
+
+       DBG_INFO("SSSP: Connecting to socket: %s\n",
+               config->socket_path);
+
+       become_root();
+       ok = virusfilter_io_connect_path(io_h, config->socket_path);
+       unbecome_root();
+
+       if (!ok) {
+               DBG_ERR("SSSP: Connecting to socket failed: %s: %s\n",
+                       config->socket_path, strerror(errno));
+               return VIRUSFILTER_RESULT_ERROR;
+       }
+
+       ok = virusfilter_io_readl(talloc_tos(), io_h, &reply);
+       if (!ok) {
+               DBG_ERR("SSSP: Reading greeting message failed: %s\n",
+                       strerror(errno));
+               goto virusfilter_sophos_scan_init_failed;
+       }
+       ret = strncmp(reply, "OK SSSP/1.0", 11);
+       if (ret != 0) {
+               DBG_ERR("SSSP: Invalid greeting message: %s\n",
+                       reply);
+               goto virusfilter_sophos_scan_init_failed;
+       }
+
+       DBG_DEBUG("SSSP: Connected\n");
+
+       DBG_INFO("SSSP: Configuring\n");
+
+       TALLOC_FREE(reply);
+
+       ok = virusfilter_io_writefl_readl(io_h, &reply,
+           "SSSP/1.0 OPTIONS\noutput:brief\nsavigrp:GrpArchiveUnpack %d\n",
+           config->scan_archive ? 1 : 0);
+       if (!ok) {
+               DBG_ERR("SSSP: OPTIONS: I/O error: %s\n", strerror(errno));
+               goto virusfilter_sophos_scan_init_failed;
+       }
+       ret = strncmp(reply, "ACC ", 4);
+       if (ret != 0) {
+               DBG_ERR("SSSP: OPTIONS: Not accepted: %s\n", reply);
+               goto virusfilter_sophos_scan_init_failed;
+       }
+
+       TALLOC_FREE(reply);
+
+       ok = virusfilter_io_readl(talloc_tos(), io_h, &reply);
+       if (!ok) {
+               DBG_ERR("SSSP: OPTIONS: Read error: %s\n", strerror(errno));
+               goto virusfilter_sophos_scan_init_failed;
+       }
+       ret = strncmp(reply, "DONE OK ", 8);
+       if (ret != 0) {
+               DBG_ERR("SSSP: OPTIONS failed: %s\n", reply);
+               goto virusfilter_sophos_scan_init_failed;
+       }
+
+       TALLOC_FREE(reply);
+
+       ok = virusfilter_io_readl(talloc_tos(), io_h, &reply);
+       if (!ok) {
+               DBG_ERR("SSSP: OPTIONS: Read error: %s\n", strerror(errno));
+               goto virusfilter_sophos_scan_init_failed;
+       }
+       ret = strcmp(reply, "");
+       if (ret != 0) {
+               DBG_ERR("SSSP: OPTIONS: Invalid reply: %s\n", reply);
+               goto virusfilter_sophos_scan_init_failed;
+       }
+
+       DBG_DEBUG("SSSP: Configured\n");
+
+       return VIRUSFILTER_RESULT_OK;
+
+virusfilter_sophos_scan_init_failed:
+
+       TALLOC_FREE(reply);
+
+       virusfilter_sophos_scan_end(config);
+
+       return VIRUSFILTER_RESULT_ERROR;
+}
+
+static void virusfilter_sophos_scan_end(
+       struct virusfilter_config *config)
+{
+       struct virusfilter_io_handle *io_h = config->io_h;
+
+       DBG_INFO("SSSP: Disconnecting\n");
+
+       virusfilter_io_disconnect(io_h);
+}
+
+static virusfilter_result virusfilter_sophos_scan(
+       struct vfs_handle_struct *handle,
+       struct virusfilter_config *config,
+       const struct files_struct *fsp,
+       char **reportp)
+{
+       char *cwd_fname = fsp->conn->cwd_fname->base_name;
+       const char *fname = fsp->fsp_name->base_name;
+       char fileurl[VIRUSFILTER_IO_URL_MAX+1];
+       int fileurl_len, fileurl_len2;
+       struct virusfilter_io_handle *io_h = config->io_h;
+       virusfilter_result result = VIRUSFILTER_RESULT_ERROR;
+       char *report = NULL;
+       char *reply = NULL;
+       char *reply_token, *reply_saveptr;
+       int ret;
+       bool ok;
+
+       DBG_INFO("Scanning file: %s/%s\n", cwd_fname, fname);
+
+       fileurl_len = virusfilter_url_quote(cwd_fname, fileurl,
+                                           VIRUSFILTER_IO_URL_MAX);
+       if (fileurl_len < 0) {
+               DBG_ERR("virusfilter_url_quote failed: File path too long: "
+                       "%s/%s\n", cwd_fname, fname);
+               result = VIRUSFILTER_RESULT_ERROR;
+               report = talloc_asprintf(talloc_tos(), "File path too long");
+               goto virusfilter_sophos_scan_return;
+       }
+       fileurl[fileurl_len] = '/';
+       fileurl_len++;
+
+       fileurl_len += fileurl_len2 = virusfilter_url_quote(fname,
+               fileurl + fileurl_len, VIRUSFILTER_IO_URL_MAX - fileurl_len);
+       if (fileurl_len2 < 0) {
+               DBG_ERR("virusfilter_url_quote failed: File path too long: "
+                       "%s/%s\n", cwd_fname, fname);
+               result = VIRUSFILTER_RESULT_ERROR;
+               report = talloc_asprintf(talloc_tos(), "File path too long");
+               goto virusfilter_sophos_scan_return;
+       }
+       fileurl_len += fileurl_len2;
+
+       ok = virusfilter_io_writevl(io_h, "SSSP/1.0 SCANFILE ", 18, fileurl,
+                                   fileurl_len, NULL);
+       if (!ok) {
+               DBG_ERR("SSSP: SCANFILE: Write error: %s\n",
+                     strerror(errno));
+               goto virusfilter_sophos_scan_io_error;
+       }
+
+       ok = virusfilter_io_readl(talloc_tos(), io_h, &reply);
+       if (!ok) {
+               DBG_ERR("SSSP: SCANFILE: Read error: %s\n", strerror(errno));
+               goto virusfilter_sophos_scan_io_error;
+       }
+       ret = strncmp(reply, "ACC ", 4);
+       if (ret != 0) {
+               DBG_ERR("SSSP: SCANFILE: Not accepted: %s\n",
+                       reply);
+               result = VIRUSFILTER_RESULT_ERROR;
+               goto virusfilter_sophos_scan_return;
+       }
+
+       TALLOC_FREE(reply);
+
+       result = VIRUSFILTER_RESULT_CLEAN;
+       for (;;) {
+               ok = virusfilter_io_readl(talloc_tos(), io_h, &reply);
+               if (!ok) {
+                       DBG_ERR("SSSP: SCANFILE: Read error: %s\n",
+                               strerror(errno));
+                       goto virusfilter_sophos_scan_io_error;
+               }
+
+               ret = strcmp(reply, "");
+               if (ret == 0) {
+                       break;
+               }
+
+               reply_token = strtok_r(reply, " ", &reply_saveptr);
+
+               if (strcmp(reply_token, "VIRUS") == 0) {
+                       result = VIRUSFILTER_RESULT_INFECTED;
+                       reply_token = strtok_r(NULL, " ", &reply_saveptr);
+                       if (reply_token != NULL) {
+                                 report = talloc_strdup(talloc_tos(),
+                                                        reply_token);
+                       } else {
+                                 report = talloc_asprintf(talloc_tos(),
+                                                       "UNKNOWN INFECTION");
+                       }
+               } else if (strcmp(reply_token, "OK") == 0) {
+
+                       /* Ignore */
+               } else if (strcmp(reply_token, "DONE") == 0) {
+                       reply_token = strtok_r(NULL, "", &reply_saveptr);
+                       if (reply_token != NULL &&
+
+                           /* Succeed */
+                           strncmp(reply_token, "OK 0000 ", 8) != 0 &&
+
+                           /* Infected */
+                           strncmp(reply_token, "OK 0203 ", 8) != 0)
+                       {
+                               DBG_ERR("SSSP: SCANFILE: Error: %s\n",
+                                       reply_token);
+                               result = VIRUSFILTER_RESULT_ERROR;
+                               report = talloc_asprintf(talloc_tos(),
+                                                        "Scanner error: %s\n",
+                                                        reply_token);
+                       }
+               } else {
+                       DBG_ERR("SSSP: SCANFILE: Invalid reply: %s\n",
+                               reply_token);
+                       result = VIRUSFILTER_RESULT_ERROR;
+                       report = talloc_asprintf(talloc_tos(), "Scanner "
+                                                "communication error");
+               }
+
+               TALLOC_FREE(reply);
+       }
+
+virusfilter_sophos_scan_return:
+       TALLOC_FREE(reply);
+
+       if (report == NULL) {
+               *reportp = talloc_asprintf(talloc_tos(),
+                                          "Scanner report memory error");
+       } else {
+               *reportp = report;
+       }
+
+       return result;
+
+virusfilter_sophos_scan_io_error:
+       *reportp = talloc_asprintf(talloc_tos(),
+                                  "Scanner I/O error: %s\n", strerror(errno));
+
+       return result;
+}
+
+static struct virusfilter_backend_fns virusfilter_backend_sophos ={
+       .connect = virusfilter_sophos_connect,
+       .disconnect = NULL,
+       .scan_init = virusfilter_sophos_scan_init,
+       .scan = virusfilter_sophos_scan,
+       .scan_end = virusfilter_sophos_scan_end,
+};
+
+int virusfilter_sophos_init(struct virusfilter_config *config)
+{
+       struct virusfilter_backend *backend = NULL;
+
+       if (config->socket_path == NULL) {
+               config->socket_path = VIRUSFILTER_DEFAULT_SOCKET_PATH;
+       }
+
+       backend = talloc_zero(config, struct virusfilter_backend);
+       if (backend == NULL) {
+               return -1;
+       }
+
+       backend->fns = &virusfilter_backend_sophos;
+       backend->name = "sophos";
+
+       config->backend = backend;
+       return 0;
+}
index f417947..14fddb3 100644 (file)
@@ -512,7 +512,10 @@ bld.SAMBA3_MODULE('vfs_snapper',
 
 bld.SAMBA3_MODULE('vfs_virusfilter',
                  subsystem='vfs',
-                 source='vfs_virusfilter.c',
+                 source='''
+                 vfs_virusfilter.c
+                 vfs_virusfilter_sophos.c
+                 ''',
                  deps='samba-util VFS_VIRUSFILTER_UTILS',
                  init_function='',
                  internal_module=bld.SAMBA3_IS_STATIC_MODULE('vfs_virusfilter'),