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