/*
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 .
*/
#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 = NULL, *reply_saveptr = NULL;
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 */
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\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 != 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\t0x\t [] */
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;
}