/*
   Samba-VirusFilter VFS modules
   Copyright (C) 2010-2016 SATOH Fumiyasu @ OSS Technology Corp., Japan
   Copyright (C) 2016-2017 Trever L. Adams

   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 "modules/vfs_virusfilter_common.h"
#include "modules/vfs_virusfilter_utils.h"

struct iovec;

#include "lib/util/iov_buf.h"
#include <tevent.h>
#include "lib/tsocket/tsocket.h"
#include "source3/lib/substitute.h"

int virusfilter_debug_class = DBGC_VFS;

/* ====================================================================== */

char *virusfilter_string_sub(
	TALLOC_CTX *mem_ctx,
	connection_struct *conn,
	const char *str)
{
	const struct loadparm_substitution *lp_sub =
		loadparm_s3_global_substitution();

	return talloc_sub_full(mem_ctx,
		lp_servicename(mem_ctx, lp_sub, SNUM(conn)),
		conn->session_info->unix_info->unix_name,
		conn->connectpath,
		conn->session_info->unix_token->gid,
		conn->session_info->unix_info->sanitized_username,
		conn->session_info->info->domain_name,
		str);
}

int virusfilter_vfs_next_move(
	struct 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_RENAMEAT(vfs_h,
			vfs_h->conn->cwd_fsp,
			smb_fname_src,
			vfs_h->conn->cwd_fsp,
			smb_fname_dst);
	if (result == 0 || errno != EXDEV) {
		return result;
	}

	/*
	 * For now, do not handle EXDEV as poking around violates
	 * stackability. Return -1, simply refuse access.
	 */
	return -1;
}

/* Line-based socket I/O
 * ======================================================================
 */

struct virusfilter_io_handle *virusfilter_io_new(
	TALLOC_CTX *mem_ctx,
	int connect_timeout,
	int io_timeout)
{
	struct virusfilter_io_handle *io_h = talloc_zero(mem_ctx,
						struct virusfilter_io_handle);

	if (io_h == NULL) {
		return NULL;
	}

	io_h->stream = NULL;
	io_h->r_len = 0;

	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(
	struct 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(
	struct 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(
	struct 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(
	struct 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;
}

bool virusfilter_io_connect_path(
	struct virusfilter_io_handle *io_h,
	const char *path)
{
	struct sockaddr_un addr;
	NTSTATUS status;
	int socket, ret;
	size_t len;
	bool ok;

	ZERO_STRUCT(addr);
	addr.sun_family = AF_UNIX;

	len = strlcpy(addr.sun_path, path, sizeof(addr.sun_path));
	if (len >= sizeof(addr.sun_path)) {
		io_h->stream = NULL;
		return false;
	}

	status = open_socket_out((struct sockaddr_storage *)&addr, 0,
				 io_h->connect_timeout,
				 &socket);
	if (!NT_STATUS_IS_OK(status)) {
		io_h->stream = NULL;
		return false;
	}

	/* We must not block */
	ret = set_blocking(socket, false);
	if (ret == -1) {
		close(socket);
		io_h->stream = NULL;
		return false;
	}

	ok = smb_set_close_on_exec(socket);
	if (!ok) {
		close(socket);
		io_h->stream = NULL;
		return false;
	}

	ret = tstream_bsd_existing_socket(io_h, socket, &io_h->stream);
	if (ret == -1) {
		close(socket);
		DBG_ERR("Could not convert socket to tstream: %s.\n",
			strerror(errno));
		io_h->stream = NULL;
		return false;
	}

	return true;
}

static void disconnect_done(struct tevent_req *req)
{
	uint64_t *perr = tevent_req_callback_data(req, uint64_t);
	int ret;
	int err_ret;

	ret = tstream_disconnect_recv(req, &err_ret);
	TALLOC_FREE(req);
	if (ret == -1) {
		*perr = err_ret;
	}
}

bool virusfilter_io_disconnect(
	struct virusfilter_io_handle *io_h)
{
	struct tevent_req *req;
	struct tevent_context *ev;
	uint64_t *perror = NULL;
	bool ok = true;
	TALLOC_CTX *frame = talloc_stackframe();

	if (io_h->stream == NULL) {
		io_h->r_len = 0;
		TALLOC_FREE(frame);
		return VIRUSFILTER_RESULT_OK;
	}

	ev = tevent_context_init(frame);
	if (ev == NULL) {
		DBG_ERR("Failed to setup event context.\n");
		ok = false;
		goto fail;
	}

	/* Error return - must be talloc'ed. */
	perror = talloc_zero(frame, uint64_t);
	if (perror == NULL) {
		goto fail;
	}

	req = tstream_disconnect_send(io_h, ev, io_h->stream);

	/* Callback when disconnect is done. */
	tevent_req_set_callback(req, disconnect_done, perror);

	/* Set timeout. */
	ok = tevent_req_set_endtime(req, ev, timeval_current_ofs_msec(
				    io_h->connect_timeout));
	if (!ok) {
		DBG_ERR("Can't set endtime\n");
		goto fail;
	}

	/* Loop waiting for req to finish. */
	ok = tevent_req_poll(req, ev);
	if (!ok) {
		DBG_ERR("tevent_req_poll failed\n");
		goto fail;
	}

	/* Emit debug error if failed. */
	if (*perror != 0) {
		DBG_DEBUG("Error %s\n", strerror((int)*perror));
		goto fail;
	}

	/* Here we know we disconnected. */

	io_h->stream = NULL;
	io_h->r_len = 0;

	fail:
		TALLOC_FREE(frame);
		return ok;
}

static void writev_done(struct tevent_req *req)
{
	uint64_t *perr = tevent_req_callback_data(req, uint64_t);
	int ret;
	int err_ret;

	ret = tstream_writev_recv(req, &err_ret);
	TALLOC_FREE(req);
	if (ret == -1) {
		*perr = err_ret;
	}
}

/****************************************************************************
 Write all data from an iov array, with msec timeout (per write)
 NB. This can be called with a non-socket fd, don't add dependencies
 on socket calls.
****************************************************************************/

bool write_data_iov_timeout(
	struct tstream_context *stream,
	const struct iovec *iov,
	size_t iovcnt,
	int ms_timeout)
{
	struct tevent_context *ev = NULL;
	struct tevent_req *req = NULL;
	uint64_t *perror = NULL;
	bool ok = false;
	TALLOC_CTX *frame = talloc_stackframe();

	ev = tevent_context_init(frame);
	if (ev == NULL) {
		DBG_ERR("Failed to setup event context.\n");
		goto fail;
	}

	/* Error return - must be talloc'ed. */
	perror = talloc_zero(frame, uint64_t);
	if (perror == NULL) {
		goto fail;
	}

	/* Send the data. */
	req = tstream_writev_send(frame, ev, stream, iov, iovcnt);
	if (req == NULL) {
		DBG_ERR("Out of memory.\n");
		goto fail;
	}

	/* Callback when *all* data sent. */
	tevent_req_set_callback(req, writev_done, perror);

	/* Set timeout. */
	ok = tevent_req_set_endtime(req, ev,
				    timeval_current_ofs_msec(ms_timeout));
	if (!ok) {
		DBG_ERR("Can't set endtime\n");
		goto fail;
	}

	/* Loop waiting for req to finish. */
	ok = tevent_req_poll(req, ev);
	if (!ok) {
		DBG_ERR("tevent_req_poll failed\n");
		goto fail;
	}

	/* Done with req - freed by the callback. */
	req = NULL;

	/* Emit debug error if failed. */
	if (*perror != 0) {
		DBG_DEBUG("Error %s\n", strerror((int)*perror));
		goto fail;
	}

	/* Here we know we correctly wrote all data. */
	TALLOC_FREE(frame);
	return true;

  fail:
	TALLOC_FREE(frame);
	return false;
}

bool virusfilter_io_write(
	struct virusfilter_io_handle *io_h,
	const char *data,
	size_t data_size)
{
	struct iovec iov;

	if (data_size == 0) {
		return VIRUSFILTER_RESULT_OK;
	}

	iov.iov_base = discard_const_p(void, data);
	iov.iov_len = data_size;

	return write_data_iov_timeout(io_h->stream, &iov, 1, io_h->io_timeout);
}

bool virusfilter_io_writel(
	struct virusfilter_io_handle *io_h,
	const char *data,
	size_t data_size)
{
	bool ok;

	ok = virusfilter_io_write(io_h, data, data_size);
	if (!ok) {
		return ok;
	}

	return virusfilter_io_write(io_h, io_h->w_eol, io_h->w_eol_size);
}

bool PRINTF_ATTRIBUTE(2, 3) virusfilter_io_writefl(
	struct virusfilter_io_handle *io_h,
	const char *data_fmt, ...)
{
	va_list ap;
	char data[VIRUSFILTER_IO_BUFFER_SIZE + VIRUSFILTER_IO_EOL_SIZE];
	int data_size;

	va_start(ap, data_fmt);
	data_size = vsnprintf(data, VIRUSFILTER_IO_BUFFER_SIZE, data_fmt, ap);
	va_end(ap);

	if (unlikely (data_size < 0)) {
		DBG_ERR("vsnprintf failed: %s\n", strerror(errno));
		return false;
	}

	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);
}

bool PRINTF_ATTRIBUTE(2, 0) virusfilter_io_vwritefl(
	struct virusfilter_io_handle *io_h,
	const char *data_fmt, va_list ap)
{
	char data[VIRUSFILTER_IO_BUFFER_SIZE + VIRUSFILTER_IO_EOL_SIZE];
	int data_size;

	data_size = vsnprintf(data, VIRUSFILTER_IO_BUFFER_SIZE, data_fmt, ap);

	if (unlikely (data_size < 0)) {
		DBG_ERR("vsnprintf failed: %s\n", strerror(errno));
		return false;
	}

	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);
}

bool virusfilter_io_writev(
	struct 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 == NULL) {
			break;
		}
		iov_p->iov_len = va_arg(ap, int);
	}
	va_end(ap);

	return write_data_iov_timeout(io_h->stream, iov, iov_n,
		io_h->io_timeout);
}

bool virusfilter_io_writevl(
	struct 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 == NULL) {
			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++;

	return write_data_iov_timeout(io_h->stream, iov, iov_n,
		io_h->io_timeout);
}

static bool return_existing_line(TALLOC_CTX *ctx,
				struct virusfilter_io_handle *io_h,
				char **read_line)
{
	size_t read_line_len = 0;
	char *end_p = NULL;
	char *eol = NULL;

	eol = memmem(io_h->r_buffer, io_h->r_len,
			io_h->r_eol, io_h->r_eol_size);
	if (eol == NULL) {
		return false;
	}
	end_p = eol + io_h->r_eol_size;

	*eol = '\0';
	read_line_len = strlen(io_h->r_buffer) + 1;
	*read_line = talloc_memdup(ctx,
				io_h->r_buffer,
				read_line_len);
	if (*read_line == NULL) {
		return false;
	}

	/*
	 * Copy the remaining buffer over the line
	 * we returned.
	 */
	memmove(io_h->r_buffer,
		end_p,
		io_h->r_len - (end_p - io_h->r_buffer));

	/* And reduce the size left in the buffer. */
	io_h->r_len -= (end_p - io_h->r_buffer);
	return true;
}

static void readv_done(struct tevent_req *req)
{
	uint64_t *perr = tevent_req_callback_data(req, uint64_t);
	int ret;
	int err_ret;

	ret = tstream_readv_recv(req, &err_ret);
	TALLOC_FREE(req);
	if (ret == -1) {
		*perr = err_ret;
	}
}

bool virusfilter_io_readl(TALLOC_CTX *ctx,
			struct virusfilter_io_handle *io_h,
			char **read_line)
{
	struct tevent_context *ev = NULL;
	bool ok = false;
	uint64_t *perror = NULL;
	TALLOC_CTX *frame = talloc_stackframe();

	/* Search for an existing complete line. */
	ok = return_existing_line(ctx, io_h, read_line);
	if (ok) {
		goto finish;
	}

	/*
	 * No complete line in the buffer. We must read more
	 * from the server.
	 */
	ev = tevent_context_init(frame);
	if (ev == NULL) {
		DBG_ERR("Failed to setup event context.\n");
		goto finish;
	}

	/* Error return - must be talloc'ed. */
	perror = talloc_zero(frame, uint64_t);
	if (perror == NULL) {
		goto finish;
	}

	for (;;) {
		ssize_t pending = 0;
		size_t read_size = 0;
		struct iovec iov;
		struct tevent_req *req = NULL;

		/*
		 * How much can we read ?
		 */
		pending = tstream_pending_bytes(io_h->stream);
		if (pending < 0) {
			DBG_ERR("tstream_pending_bytes failed (%s).\n",
				strerror(errno));
			goto finish;
		}

		read_size = pending;
		/* Must read at least one byte. */
		read_size = MIN(read_size, 1);

		/* And max remaining buffer space. */
		read_size = MAX(read_size,
				(sizeof(io_h->r_buffer) - io_h->r_len));

		if (read_size == 0) {
			/* Buffer is full with no EOL. Error out. */
			DBG_ERR("Line buffer full.\n");
			goto finish;
		}

		iov.iov_base = io_h->r_buffer + io_h->r_len;
		iov.iov_len = read_size;

		/* Read the data. */
		req = tstream_readv_send(frame,
					ev,
					io_h->stream,
					&iov,
					1);
		if (req == NULL) {
			DBG_ERR("out of memory.\n");
			goto finish;
		}

		/* Callback when *all* data read. */
		tevent_req_set_callback(req, readv_done, perror);

		/* Set timeout. */
		ok = tevent_req_set_endtime(req, ev,
				timeval_current_ofs_msec(io_h->io_timeout));
		if (!ok) {
			DBG_ERR("can't set endtime\n");
			goto finish;
		}

		/* Loop waiting for req to finish. */
		ok = tevent_req_poll(req, ev);
		if (!ok) {
			DBG_ERR("tevent_req_poll failed\n");
			goto finish;
		}

		/* Done with req - freed by the callback. */
		req = NULL;

		/*
		 * Emit debug error if failed.
		 * EPIPE may be success so, don't exit.
		 */
		if (*perror != 0 && *perror != EPIPE) {
			DBG_DEBUG("Error %s\n", strerror((int)*perror));
			errno = (int)*perror;
			goto finish;
		}

		/*
		 * We read read_size bytes. Extend the usable
		 * buffer length.
		 */
		io_h->r_len += read_size;

		/* Paranoia... */
		SMB_ASSERT(io_h->r_len <= sizeof(io_h->r_buffer));

		/* Exit if we have a line to return. */
		ok = return_existing_line(ctx, io_h, read_line);
		if (ok) {
			goto finish;
		}
		/* No eol - keep reading. */
	}

  finish:

	TALLOC_FREE(frame);
	return ok;
}

bool PRINTF_ATTRIBUTE(3, 4) virusfilter_io_writefl_readl(
	struct virusfilter_io_handle *io_h,
	char **read_line,
	const char *fmt, ...)
{
	bool ok;

	if (fmt) {
		va_list ap;

		va_start(ap, fmt);
		ok = virusfilter_io_vwritefl(io_h, fmt, ap);
		va_end(ap);

		if (!ok) {
			return ok;
		}
	}

	ok = virusfilter_io_readl(talloc_tos(), io_h, read_line);
	if (!ok) {
		DBG_ERR("virusfilter_io_readl not OK: %d\n", ok);
		return false;
	}
	if (io_h->r_len == 0) { /* EOF */
		DBG_ERR("virusfilter_io_readl EOF\n");
		return false;
	}

	return true;
}

struct virusfilter_cache *virusfilter_cache_new(
	TALLOC_CTX *ctx,
	int entry_limit,
	time_t time_limit)
{
	struct virusfilter_cache *cache;

	if (time_limit == 0) {
		return NULL;
	}

	cache = talloc_zero(ctx, struct virusfilter_cache);
	if (cache == NULL) {
		DBG_ERR("talloc_zero failed.\n");
		return NULL;
	}

	cache->cache = memcache_init(cache->ctx, entry_limit *
				       (sizeof(struct virusfilter_cache_entry)
				       + VIRUSFILTER_CACHE_BUFFER_SIZE));
	if (cache->cache == NULL) {
		DBG_ERR("memcache_init failed.\n");
		return NULL;
	}
	cache->ctx = ctx;
	cache->time_limit = time_limit;

	return cache;
}

bool virusfilter_cache_entry_add(
	struct virusfilter_cache *cache,
	const char *directory,
	const char *fname,
	virusfilter_result result,
	char *report)
{
	int blob_size = sizeof(struct virusfilter_cache_entry);
	struct virusfilter_cache_entry *cache_e =
					talloc_zero_size(NULL, blob_size);
	int fname_len = 0;

	if (fname == NULL || directory == NULL) {
		TALLOC_FREE(report);
		return false;
	}

	fname = talloc_asprintf(talloc_tos(), "%s/%s", directory, fname);

	if (fname == NULL) {
		TALLOC_FREE(report);
		return false;
	}

	fname_len = strlen(fname);

	if (cache_e == NULL|| cache->time_limit == 0) {
		TALLOC_FREE(report);
		return false;
	}

	cache_e->result = result;
	if (report != NULL) {
		cache_e->report = talloc_steal(cache_e, report);
	}
	if (cache->time_limit > 0) {
		cache_e->time = time(NULL);
	}

	memcache_add_talloc(cache->cache,
			    VIRUSFILTER_SCAN_RESULTS_CACHE_TALLOC,
			    data_blob_const(fname, fname_len), &cache_e);

	return true;
}

bool virusfilter_cache_entry_rename(
	struct virusfilter_cache *cache,
	const char *directory,
	char *old_fname,
	char *new_fname)
{
	int old_fname_len = 0;
	int new_fname_len = 0;
	struct virusfilter_cache_entry *new_data = NULL;
	struct virusfilter_cache_entry *old_data = NULL;

	if (old_fname == NULL || new_fname == NULL || directory == NULL) {
		return false;
	}

	old_fname = talloc_asprintf(talloc_tos(), "%s/%s", directory, old_fname);
	new_fname = talloc_asprintf(talloc_tos(), "%s/%s", directory, new_fname);

	if (old_fname == NULL || new_fname == NULL) {
		TALLOC_FREE(old_fname);
		TALLOC_FREE(new_fname);
		return false;
	}

	old_fname_len = strlen(old_fname);
	new_fname_len = strlen(new_fname);

	old_data = memcache_lookup_talloc(
				cache->cache,
				VIRUSFILTER_SCAN_RESULTS_CACHE_TALLOC,
				data_blob_const(old_fname, old_fname_len));

	if (old_data == NULL) {
		return false;
	}

	new_data = talloc_memdup(cache->ctx, old_data,
				 sizeof(struct virusfilter_cache_entry));
	if (new_data == NULL) {
		return false;
	}
	new_data->report = talloc_strdup(new_data, old_data->report);

	memcache_add_talloc(cache->cache,
			VIRUSFILTER_SCAN_RESULTS_CACHE_TALLOC,
			data_blob_const(new_fname, new_fname_len), &new_data);

	memcache_delete(cache->cache, VIRUSFILTER_SCAN_RESULTS_CACHE_TALLOC,
			data_blob_const(old_fname, old_fname_len));

	return true;
}

void virusfilter_cache_purge(struct virusfilter_cache *cache)
{
	memcache_flush(cache->cache, VIRUSFILTER_SCAN_RESULTS_CACHE_TALLOC);
}

struct virusfilter_cache_entry *virusfilter_cache_get(
	struct virusfilter_cache *cache,
	const char *directory,
	const char *fname)
{
	int fname_len = 0;
	struct virusfilter_cache_entry *cache_e = NULL;
	struct virusfilter_cache_entry *data = NULL;

	if (fname == NULL || directory == NULL) {
		return 0;
	}

	fname = talloc_asprintf(talloc_tos(), "%s/%s", directory, fname);

	if (fname == NULL) {
		return 0;
	}

	fname_len = strlen(fname);

	data = memcache_lookup_talloc(cache->cache,
				      VIRUSFILTER_SCAN_RESULTS_CACHE_TALLOC,
				      data_blob_const(fname, fname_len));

	if (data == NULL) {
		return cache_e;
	}

	if (cache->time_limit > 0) {
		if (time(NULL) - data->time  > cache->time_limit) {
			DBG_DEBUG("Cache entry is too old: %s\n",
				  fname);
			virusfilter_cache_remove(cache, directory, fname);
			return cache_e;
		}
	}
	cache_e = talloc_memdup(cache->ctx, data,
			       sizeof(struct virusfilter_cache_entry));
	if (cache_e == NULL) {
		return NULL;
	}
	if (data->report != NULL) {
		cache_e->report = talloc_strdup(cache_e, data->report);
	} else {
		cache_e->report = NULL;
	}

	return cache_e;
}

void virusfilter_cache_remove(struct virusfilter_cache *cache,
	const char *directory,
	const char *fname)
{
	DBG_DEBUG("Purging cache entry: %s/%s\n", directory, fname);

	if (fname == NULL || directory == NULL) {
		return;
	}

	fname = talloc_asprintf(talloc_tos(), "%s/%s", directory, fname);

	if (fname == NULL) {
		return;
	}

	memcache_delete(cache->cache, VIRUSFILTER_SCAN_RESULTS_CACHE_TALLOC,
			data_blob_const(fname, strlen(fname)));
}

void virusfilter_cache_entry_free(struct 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) {
		DBG_ERR("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);
	char *server_addr_p;
	char *client_addr_p;
	const char *local_machine_name = get_local_machine_name();
	fstring pidstr;
	int ret;

	server_addr_p = tsocket_address_inet_addr_string(
				conn->sconn->local_address, talloc_tos());

	if (server_addr_p != NULL) {
		ret = strncmp("::ffff:", server_addr_p, 7);
		if (ret == 0) {
			server_addr_p += 7;
		}
		virusfilter_env_set(mem_ctx, env_list, "VIRUSFILTER_SERVER_IP",
				    server_addr_p);
	}
	TALLOC_FREE(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->cwd_fsp->fsp_name->base_name);

	client_addr_p = tsocket_address_inet_addr_string(
				conn->sconn->remote_address, talloc_tos());

	if (client_addr_p != NULL) {
		ret = strncmp("::ffff:", client_addr_p, 7);
		if (ret == 0) {
			client_addr_p += 7;
		}
		virusfilter_env_set(mem_ctx, env_list, "VIRUSFILTER_CLIENT_IP",
				    client_addr_p);
	}
	TALLOC_FREE(client_addr_p);

	virusfilter_env_set(mem_ctx, env_list, "VIRUSFILTER_CLIENT_NAME",
			    conn->sconn->remote_hostname);
	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",
			    get_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)
{
	int ret;

	if (conn != NULL) {
		ret = virusfilter_shell_set_conn_env(mem_ctx, env_list, conn);
		if (ret == -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));
	}
}
