#include <stdarg.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <errno.h>
#include <time.h>
#include <fcntl.h>
#include <syslog.h>
#include <ctype.h>
#include <strings.h>
#include <string.h>
#include <limits.h>
#include <pthread.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <sys/debug.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include "vs_incl.h"
#include "vs_icap.h"
static int vs_icap_option_request(vs_scan_ctx_t *);
static int vs_icap_send_option_req(vs_scan_ctx_t *);
static int vs_icap_read_option_resp(vs_scan_ctx_t *);
static int vs_icap_respmod_request(vs_scan_ctx_t *);
static int vs_icap_may_preview(vs_scan_ctx_t *);
static char *vs_icap_find_ext(char *);
static int vs_icap_send_preview(vs_scan_ctx_t *);
static int vs_icap_send_respmod_hdr(vs_scan_ctx_t *, int);
static int vs_icap_create_respmod_hdr(vs_scan_ctx_t *, int);
static int vs_icap_uri_encode(char *, int, char *);
static int vs_icap_uri_illegal_char(char);
static int vs_icap_read_respmod_resp(vs_scan_ctx_t *);
static int vs_icap_read_resp_code(vs_scan_ctx_t *);
static int vs_icap_read_hdr(vs_scan_ctx_t *, vs_hdr_t *, int);
static int vs_icap_set_scan_result(vs_scan_ctx_t *);
static int vs_icap_read_encap_hdr(vs_scan_ctx_t *);
static void vs_icap_read_encap_data(vs_scan_ctx_t *);
static int vs_icap_create_repair_file(vs_scan_ctx_t *);
static int vs_icap_read_resp_body(vs_scan_ctx_t *);
static int vs_icap_read_body_chunk(vs_scan_ctx_t *);
static int vs_icap_send_chunk(vs_scan_ctx_t *, int);
static int vs_icap_send_termination(vs_scan_ctx_t *);
static int vs_icap_readline(vs_scan_ctx_t *, char *, int);
static int vs_icap_write(int, char *, int);
static int vs_icap_read(int, char *, int);
static void vs_icap_parse_hdrs(char, char *, char **, char **);
static int vs_icap_opt_value(vs_scan_ctx_t *, int, char *);
static int vs_icap_opt_ext(vs_scan_ctx_t *, int, char *);
static int vs_icap_resp_violations(vs_scan_ctx_t *, int, char *);
static int vs_icap_resp_violation_rec(vs_scan_ctx_t *, int);
static int vs_icap_resp_infection(vs_scan_ctx_t *, int, char *);
static int vs_icap_resp_virus_id(vs_scan_ctx_t *, int, char *);
static int vs_icap_resp_encap(vs_scan_ctx_t *, int, char *);
static int vs_icap_resp_istag(vs_scan_ctx_t *, int, char *);
static void vs_icap_istag_to_scanstamp(char *, vs_scanstamp_t);
static void vs_icap_free_options(vs_options_t *);
static void vs_icap_copy_options(vs_options_t *, vs_options_t *);
static void vs_icap_update_options(vs_scan_ctx_t *);
static int vs_icap_compare_se(int, char *, int);
static iovec_t *vs_icap_make_strvec(char *, const char *);
static iovec_t *vs_icap_copy_strvec(iovec_t *);
static int vs_icap_check_ext(char *, iovec_t *);
static void vs_icap_trimspace(char *);
static char *vs_icap_resp_str(int);
vs_hdr_t option_hdrs[] = {
{ VS_OPT_SERVICE, "Service", vs_icap_opt_value},
{ VS_OPT_ISTAG, "ISTag", vs_icap_opt_value},
{ VS_OPT_METHODS, "Methods", vs_icap_opt_value},
{ VS_OPT_ALLOW, "Allow", vs_icap_opt_value},
{ VS_OPT_PREVIEW, "Preview", vs_icap_opt_value},
{ VS_OPT_XFER_PREVIEW, "Transfer-Preview", vs_icap_opt_ext},
{ VS_OPT_XFER_COMPLETE, "Transfer-Complete", vs_icap_opt_ext},
{ VS_OPT_MAX_CONNECTIONS, "Max-Connections", vs_icap_opt_value},
{ VS_OPT_TTL, "Options-TTL", vs_icap_opt_value},
{ VS_OPT_X_DEF_INFO, "X-Definition-Info", vs_icap_opt_value}
};
vs_hdr_t resp_hdrs[] = {
{ VS_RESP_ENCAPSULATED, "Encapsulated", vs_icap_resp_encap},
{ VS_RESP_ISTAG, "ISTag", vs_icap_resp_istag},
{ VS_RESP_X_VIRUS_ID, "X-Virus-ID", vs_icap_resp_virus_id},
{ VS_RESP_X_INFECTION, "X-Infection-Found", vs_icap_resp_infection},
{ VS_RESP_X_VIOLATIONS, "X-Violations-Found", vs_icap_resp_violations}
};
vs_resp_msg_t icap_resp[] = {
{ VS_RESP_CONTINUE, "Continue"},
{ VS_RESP_OK, "OK"},
{ VS_RESP_CREATED, "Virus Detected and Repaired"},
{ VS_RESP_NO_CONT_NEEDED, "No Content Necessary"},
{ VS_RESP_BAD_REQ, "Bad Request"},
{ VS_RESP_FORBIDDEN, "File Infected and not repaired"},
{ VS_RESP_NOT_FOUND, "URI not found"},
{ VS_RESP_NOT_ALLOWED, "Method not allowed"},
{ VS_RESP_TIMEOUT, "Request timedout"},
{ VS_RESP_INTERNAL_ERR, "Internal server error"},
{ VS_RESP_NOT_IMPL, "Method not implemented"},
{ VS_RESP_SERV_UNAVAIL, "Service unavailable/overloaded"},
{ VS_RESP_ICAP_VER_UNSUPP, "ICAP version not supported"},
{ VS_RESP_SCAN_ERR, "Error scanning file"},
{ VS_RESP_NO_LICENSE, "No AV License"},
{ VS_RESP_RES_UNAVAIL, "Resource unavailable"},
{ VS_RESP_UNKNOWN, "Unknown Error"},
};
static const char *EXT_SEPARATOR = ",";
static vs_options_t vs_options[VS_SE_MAX];
static pthread_mutex_t vs_opt_mutex = PTHREAD_MUTEX_INITIALIZER;
void
vs_icap_init()
{
(void) pthread_mutex_lock(&vs_opt_mutex);
(void) memset(vs_options, 0, sizeof (vs_options_t));
(void) pthread_mutex_unlock(&vs_opt_mutex);
}
void
vs_icap_fini()
{
int i;
(void) pthread_mutex_lock(&vs_opt_mutex);
for (i = 0; i < VS_SE_MAX; i++)
vs_icap_free_options(&vs_options[i]);
(void) pthread_mutex_unlock(&vs_opt_mutex);
}
void
vs_icap_config(int idx, char *host, int port)
{
(void) pthread_mutex_lock(&vs_opt_mutex);
if (vs_icap_compare_se(idx, host, port) != 0) {
vs_icap_free_options(&vs_options[idx]);
(void) strlcpy(vs_options[idx].vso_host, host,
sizeof (vs_options[idx].vso_host));
vs_options[idx].vso_port = port;
}
(void) pthread_mutex_unlock(&vs_opt_mutex);
}
int
vs_icap_scan_file(vs_eng_ctx_t *eng, char *devname, char *fname,
uint64_t fsize, int flags, vs_result_t *result)
{
vs_scan_ctx_t ctx;
int fd;
fd = open(devname, O_RDONLY);
if ((fd == -1) && (errno == ENOENT)) {
(void) sleep(1);
fd = open(devname, O_RDONLY);
}
if (fd == -1) {
syslog(LOG_ERR, "Failed to open device %s - %s",
devname, strerror(errno));
result->vsr_rc = VS_RESULT_ERROR;
return (result->vsr_rc);
}
(void) memset(&ctx, 0, sizeof (vs_scan_ctx_t));
ctx.vsc_idx = eng->vse_eidx;
(void) strlcpy(ctx.vsc_host, eng->vse_host, sizeof (ctx.vsc_host));
ctx.vsc_port = eng->vse_port;
ctx.vsc_sockfd = eng->vse_sockfd;
ctx.vsc_fd = fd;
ctx.vsc_fname = fname;
ctx.vsc_fsize = fsize;
ctx.vsc_flags = flags;
ctx.vsc_result = result;
ctx.vsc_flags |= VS_NO_REPAIR;
ctx.vsc_repair = 0;
ctx.vsc_repair_fname = NULL;
ctx.vsc_repair_fd = -1;
(void) pthread_mutex_lock(&vs_opt_mutex);
if (vs_icap_compare_se(ctx.vsc_idx, ctx.vsc_host, ctx.vsc_port) == 0) {
vs_icap_copy_options(&ctx.vsc_options,
&vs_options[ctx.vsc_idx]);
}
(void) pthread_mutex_unlock(&vs_opt_mutex);
result->vsr_rc = VS_RESULT_SE_ERROR;
if (vs_icap_option_request(&ctx) == 0)
(void) vs_icap_respmod_request(&ctx);
(void) close(fd);
vs_icap_free_options(&ctx.vsc_options);
return (result->vsr_rc);
}
static int
vs_icap_option_request(vs_scan_ctx_t *ctx)
{
if (ctx->vsc_options.vso_req_time != -1 &&
((time(0) - ctx->vsc_options.vso_req_time) >
ctx->vsc_options.vso_ttl)) {
if (vs_icap_send_option_req(ctx) < 0)
return (-1);
if (vs_icap_read_option_resp(ctx) < 0)
return (-1);
vs_icap_update_options(ctx);
}
return (0);
}
static int
vs_icap_send_option_req(vs_scan_ctx_t *ctx)
{
char my_host_name[MAXHOSTNAMELEN];
int bufsp = VS_BUF_SZ;
char *buf0 = ctx->vsc_info.vsi_send_buf;
char *bufp = buf0;
int tlen;
if (gethostname(my_host_name, sizeof (my_host_name)) != 0) {
ctx->vsc_result->vsr_rc = VS_RESULT_ERROR;
return (-1);
}
(void) memset(ctx->vsc_info.vsi_send_buf, 0,
sizeof (ctx->vsc_info.vsi_send_buf));
tlen = snprintf(bufp, bufsp, "OPTIONS icap://%s:%d/%s %s\r\n",
ctx->vsc_host, ctx->vsc_port, VS_SERVICE_NAME, VS_ICAP_VER);
bufp += tlen;
bufsp -= tlen;
tlen = snprintf(bufp, bufsp, "Host: %s\r\n\r\n", my_host_name);
bufp += tlen;
if (vs_icap_write(ctx->vsc_sockfd, buf0, (bufp - buf0)) < 0)
return (-1);
return (0);
}
static int
vs_icap_read_option_resp(vs_scan_ctx_t *ctx)
{
if (vs_icap_read_resp_code(ctx) < 0)
return (-1);
if (ctx->vsc_info.vsi_icap_rc != VS_RESP_OK) {
syslog(LOG_ERR, "ICAP protocol error "
"- unexpected option response: %s",
vs_icap_resp_str(ctx->vsc_info.vsi_icap_rc));
return (-1);
}
if (vs_icap_read_hdr(ctx, option_hdrs, VS_OPT_HDR_MAX) != 0)
return (-1);
if ((ctx->vsc_options.vso_scanstamp[0] == 0) ||
(ctx->vsc_options.vso_respmod == 0) ||
(ctx->vsc_options.vso_req_time == 0)) {
syslog(LOG_ERR, "ICAP protocol error "
"- missing or invalid option response hdrs");
return (-1);
}
return (0);
}
static int
vs_icap_respmod_request(vs_scan_ctx_t *ctx)
{
int rv;
int bytes_sent, send_len;
uint64_t resid = ctx->vsc_fsize;
if (vs_icap_may_preview(ctx)) {
if ((rv = vs_icap_send_preview(ctx)) < 0)
return (-1);
if (vs_icap_read_respmod_resp(ctx) < 0)
return (-1);
if (ctx->vsc_info.vsi_icap_rc != VS_RESP_CONTINUE)
return (0);
bytes_sent = rv;
if ((ctx->vsc_fsize - (uint64_t)bytes_sent) > VS_BUF_SZ) {
send_len = VS_BUF_SZ - bytes_sent;
if ((rv = vs_icap_send_chunk(ctx, send_len)) < 0)
return (-1);
bytes_sent += rv;
}
resid -= (uint64_t)bytes_sent;
} else {
if (vs_icap_send_respmod_hdr(ctx, 0) < 0)
return (-1);
}
while (resid) {
send_len = (resid > VS_BUF_SZ) ? VS_BUF_SZ : resid;
if ((rv = vs_icap_send_chunk(ctx, send_len)) < 0)
return (-1);
if (rv == 0)
break;
resid -= (uint64_t)rv;
}
if (vs_icap_send_termination(ctx) < 0)
return (-1);
if (vs_icap_read_respmod_resp(ctx) < 0)
return (-1);
return (0);
}
static int
vs_icap_may_preview(vs_scan_ctx_t *ctx)
{
int in_list = 0;
char *ext;
vs_options_t *opts = &ctx->vsc_options;
if (opts->vso_xfer_how == VS_PREVIEW_NONE)
return (0);
if (ctx->vsc_fsize < (uint64_t)ctx->vsc_options.vso_preview_len)
return (0);
switch (opts->vso_xfer_how) {
case VS_PREVIEW_ALL:
return (1);
case VS_PREVIEW_EXCEPT:
if ((ext = vs_icap_find_ext(ctx->vsc_fname)) != 0)
in_list = vs_icap_check_ext(ext,
opts->vso_xfer_complete);
return ((in_list) ? 0 : 1);
case VS_PREVIEW_LIST:
if ((ext = vs_icap_find_ext(ctx->vsc_fname)) != 0)
in_list = vs_icap_check_ext(ext,
opts->vso_xfer_preview);
return ((in_list) ? 1 : 0);
}
return (1);
}
static char *
vs_icap_find_ext(char *fname)
{
char *last_comp, *ext_str = 0;
if ((last_comp = strrchr(fname, '/')) != 0) {
last_comp++;
} else {
last_comp = fname;
}
if ((ext_str = strrchr(last_comp, '.')) != 0) {
ext_str++;
if (strlen(ext_str) == 0)
ext_str = 0;
}
return (ext_str);
}
static int
vs_icap_send_preview(vs_scan_ctx_t *ctx)
{
int preview_len = ctx->vsc_options.vso_preview_len;
int bytes_sent;
if (vs_icap_send_respmod_hdr(ctx, 'P') < 0)
return (-1);
if ((bytes_sent = vs_icap_send_chunk(ctx, preview_len)) < 0)
return (-1);
if (bytes_sent < preview_len)
return (-1);
if (vs_icap_send_termination(ctx) < 0)
return (-1);
return (bytes_sent);
}
static int
vs_icap_send_respmod_hdr(vs_scan_ctx_t *ctx, int ispreview)
{
int len;
if ((len = vs_icap_create_respmod_hdr(ctx, ispreview)) == -1) {
ctx->vsc_result->vsr_rc = VS_RESULT_ERROR;
return (-1);
}
if (vs_icap_write(ctx->vsc_sockfd,
ctx->vsc_info.vsi_send_buf, len) < 0) {
return (-1);
}
return (0);
}
static int
vs_icap_create_respmod_hdr(vs_scan_ctx_t *ctx, int ispreview)
{
char my_host_name[MAXHOSTNAMELEN];
int hbufsp = VS_BUF_SZ;
char *hbuf0 = ctx->vsc_info.vsi_send_buf;
char *hbufp = hbuf0;
char *encap_hdr, *encap_off0, *req_hdr, *res_hdr, *res_body;
int preview_len = ctx->vsc_options.vso_preview_len;
int tlen;
if (gethostname(my_host_name, sizeof (my_host_name)) != 0) {
ctx->vsc_result->vsr_rc = VS_RESULT_ERROR;
return (-1);
}
(void) memset(hbufp, 0, hbufsp);
tlen = snprintf(hbufp, hbufsp, "RESPMOD icap://%s:%d/%s %s\r\n",
ctx->vsc_host, ctx->vsc_port, VS_SERVICE_NAME, VS_ICAP_VER);
if (tlen >= hbufsp)
return (-1);
hbufp += tlen; hbufsp -= tlen;
tlen = snprintf(hbufp, hbufsp, "Host: %s\r\n", my_host_name);
if (tlen >= hbufsp)
return (-1);
hbufp += tlen; hbufsp -= tlen;
tlen = snprintf(hbufp, hbufsp, "Allow: 204\r\n");
if (tlen >= hbufsp)
return (-1);
hbufp += tlen; hbufsp -= tlen;
if (ispreview) {
tlen = snprintf(hbufp, hbufsp, "Preview: %d\r\n", preview_len);
if (tlen >= hbufsp)
return (-1);
hbufp += tlen; hbufsp -= tlen;
}
encap_hdr = hbufp;
tlen = snprintf(hbufp, hbufsp, "%*.*s\r\n\r\n",
VS_ENCAP_SZ, VS_ENCAP_SZ, "");
if (tlen >= hbufsp)
return (-1);
hbufp += tlen; hbufsp -= tlen;
encap_off0 = hbufp;
req_hdr = hbufp;
tlen = snprintf(hbufp, hbufsp, "GET http://%s", my_host_name);
if (tlen >= hbufsp)
return (-1);
hbufp += tlen; hbufsp -= tlen;
tlen = vs_icap_uri_encode(hbufp, hbufsp, ctx->vsc_fname);
if (tlen < 0)
return (-1);
hbufp += tlen; hbufsp -= tlen;
tlen = snprintf(hbufp, hbufsp, " HTTP/1.1\r\n\r\n");
if (tlen >= hbufsp)
return (-1);
hbufp += tlen; hbufsp -= tlen;
res_hdr = hbufp;
tlen = snprintf(hbufp, hbufsp, "HTTP/1.1 200 OK\r\n");
if (tlen >= hbufsp)
return (-1);
hbufp += tlen; hbufsp -= tlen;
tlen = snprintf(hbufp, hbufsp, "Transfer-Encoding: chunked\r\n\r\n");
if (tlen >= hbufsp)
return (-1);
hbufp += tlen; hbufsp -= tlen;
res_body = hbufp;
tlen = snprintf(encap_hdr, VS_ENCAP_SZ, "Encapsulated: "
"req-hdr=%d, res-hdr=%d, res-body=%d",
req_hdr - encap_off0, res_hdr - encap_off0, res_body - encap_off0);
encap_hdr[tlen] = ' ';
return (hbufp - hbuf0);
}
static int
vs_icap_read_respmod_resp(vs_scan_ctx_t *ctx)
{
if (vs_icap_read_resp_code(ctx) < 0)
return (-1);
if (vs_icap_read_hdr(ctx, resp_hdrs, VS_RESP_HDR_MAX) < 0)
return (-1);
if (ctx->vsc_info.vsi_icap_rc == VS_RESP_CONTINUE) {
if ((ctx->vsc_info.vsi_res_hdr) ||
(ctx->vsc_info.vsi_res_body)) {
syslog(LOG_ERR, "ICAP protocol error -"
"- encapsulated data in Continue response");
return (-1);
}
} else {
if (vs_icap_set_scan_result(ctx) < 0)
return (-1);
if (ctx->vsc_info.vsi_res_hdr) {
if (vs_icap_read_encap_hdr(ctx) < 0)
return (-1);
}
if (ctx->vsc_info.vsi_res_body)
vs_icap_read_encap_data(ctx);
else if (ctx->vsc_result->vsr_rc == VS_RESULT_CLEANED)
ctx->vsc_result->vsr_rc = VS_RESULT_FORBIDDEN;
}
return (0);
}
static int
vs_icap_read_resp_code(vs_scan_ctx_t *ctx)
{
char *buf = ctx->vsc_info.vsi_recv_buf;
int retval;
for (;;) {
(void) memset(buf, '\0', VS_BUF_SZ);
if ((retval = vs_icap_readline(ctx, buf, VS_BUF_SZ)) < 0)
return (-1);
if (retval && buf[0]) {
if (MATCH(buf, VS_ICAP_VER)) {
(void) sscanf(buf+8, "%d",
&ctx->vsc_info.vsi_icap_rc);
return (0);
}
syslog(LOG_ERR, "ICAP protocol error -"
"- expected ICAP/1.0, received %s", buf);
return (-1);
}
}
}
static int
vs_icap_read_hdr(vs_scan_ctx_t *ctx, vs_hdr_t hdrs[], int num_hdrs)
{
char *buf = ctx->vsc_info.vsi_recv_buf;
int i, retval;
char *name, *val;
for (;;) {
(void) memset(buf, '\0', VS_BUF_SZ);
if ((retval = vs_icap_readline(ctx, buf, VS_BUF_SZ)) < 0)
return (-1);
if ((retval == 0) || (!buf[0]))
break;
vs_icap_parse_hdrs(':', buf, &name, &val);
for (i = 0; i < num_hdrs; i++) {
if (strcmp(name, hdrs[i].vsh_name) == 0) {
hdrs[i].vsh_func(ctx, hdrs[i].vsh_id, val);
break;
}
}
}
return ((retval >= 0) ? 0 : -1);
}
static int
vs_icap_set_scan_result(vs_scan_ctx_t *ctx)
{
int i;
vs_result_t *result = ctx->vsc_result;
(void) strlcpy(result->vsr_scanstamp,
ctx->vsc_options.vso_scanstamp, sizeof (vs_scanstamp_t));
switch (ctx->vsc_info.vsi_icap_rc) {
case VS_RESP_NO_CONT_NEEDED:
result->vsr_rc = VS_RESULT_CLEAN;
break;
case VS_RESP_OK:
if (result->vsr_nviolations == 0) {
result->vsr_rc = VS_RESULT_CLEAN;
break;
}
result->vsr_rc = VS_RESULT_CLEANED;
for (i = 0; i < result->vsr_nviolations; i++) {
if (result->vsr_vrec[i].vr_res !=
VS_RES_FILE_REPAIRED) {
result->vsr_rc = VS_RESULT_FORBIDDEN;
break;
}
}
break;
case VS_RESP_CREATED :
result->vsr_rc = VS_RESULT_CLEANED;
break;
case VS_RESP_FORBIDDEN:
result->vsr_rc = VS_RESULT_FORBIDDEN;
break;
default:
syslog(LOG_ERR, "ICAP protocol error "
"- unsupported scan result: %s",
vs_icap_resp_str(ctx->vsc_info.vsi_icap_rc));
return (-1);
}
return (0);
}
static int
vs_icap_read_encap_hdr(vs_scan_ctx_t *ctx)
{
char *buf = ctx->vsc_info.vsi_recv_buf;
char *name, *value;
int retval;
for (;;) {
if ((retval = vs_icap_readline(ctx, buf, VS_BUF_SZ)) < 0)
return (-1);
if ((retval == 0) || (!buf[0]))
break;
if (MATCH(buf, "HTTP/1.1")) {
(void) sscanf(buf + 8, "%d",
&ctx->vsc_info.vsi_http_rc);
ctx->vsc_info.vsi_html_content = B_TRUE;
if (ctx->vsc_result->vsr_rc == VS_RESULT_CLEAN) {
if ((ctx->vsc_info.vsi_icap_rc == VS_RESP_OK) &&
(ctx->vsc_info.vsi_http_rc == VS_RESP_OK)) {
ctx->vsc_result->vsr_rc =
VS_RESULT_CLEANED;
} else {
ctx->vsc_result->vsr_rc =
VS_RESULT_FORBIDDEN;
}
}
} else {
vs_icap_parse_hdrs(':', buf, &name, &value);
if (name && (MATCH(name, "Content-Length"))) {
(void) sscanf(value, "%d",
&ctx->vsc_info.vsi_content_len);
}
}
}
return (0);
}
static void
vs_icap_read_encap_data(vs_scan_ctx_t *ctx)
{
if (ctx->vsc_result->vsr_rc == VS_RESULT_CLEANED) {
ctx->vsc_result->vsr_rc = VS_RESULT_FORBIDDEN;
if (!(ctx->vsc_flags & VS_NO_REPAIR)) {
if (vs_icap_create_repair_file(ctx) == 0)
ctx->vsc_repair = B_TRUE;
}
}
(void) vs_icap_read_resp_body(ctx);
if (ctx->vsc_repair_fd != -1) {
(void) close(ctx->vsc_repair_fd);
if (ctx->vsc_repair) {
ctx->vsc_result->vsr_rc = VS_RESULT_CLEANED;
} else {
(void) unlink(ctx->vsc_repair_fname);
}
}
}
static int
vs_icap_create_repair_file(vs_scan_ctx_t *ctx)
{
if (ctx->vsc_repair_fname == NULL)
return (-1);
if ((ctx->vsc_repair_fd = open(ctx->vsc_repair_fname,
O_RDWR | O_CREAT | O_EXCL | O_TRUNC, 0644)) == -1) {
return (-1);
}
return (0);
}
static int
vs_icap_read_resp_body(vs_scan_ctx_t *ctx)
{
int retval;
while ((retval = vs_icap_read_body_chunk(ctx)) > 0)
;
if (retval < 0)
ctx->vsc_repair = B_FALSE;
return (retval);
}
static int
vs_icap_read_body_chunk(vs_scan_ctx_t *ctx)
{
char *lbuf = ctx->vsc_info.vsi_recv_buf;
unsigned int chunk_size, resid;
int rsize;
if ((vs_icap_readline(ctx, lbuf, VS_BUF_SZ) < 0) ||
(!sscanf(lbuf, "%x", &chunk_size))) {
return (-1);
}
resid = chunk_size;
while (resid) {
rsize = (resid < VS_BUF_SZ) ? resid : VS_BUF_SZ;
if ((rsize = vs_icap_read(ctx->vsc_sockfd, lbuf, rsize)) <= 0)
return (-1);
if (ctx->vsc_repair) {
if (vs_icap_write(ctx->vsc_repair_fd, lbuf, rsize) < 0)
ctx->vsc_repair = B_FALSE;
}
resid -= rsize;
}
if (vs_icap_readline(ctx, lbuf, VS_BUF_SZ) < 0)
return (-1);
if (lbuf[0]) {
syslog(LOG_ERR, "ICAP protocol error - expected blank line");
return (-1);
}
return (chunk_size);
}
static int
vs_icap_write(int fd, char *buf, int buflen)
{
char *ptr = buf;
int resid = buflen;
int bytes_sent = 0;
while (resid > 0) {
errno = 0;
bytes_sent = write(fd, ptr, resid);
if (bytes_sent < 0) {
if (errno == EINTR)
continue;
else
return (-1);
}
resid -= bytes_sent;
ptr += bytes_sent;
}
return (0);
}
static int
vs_icap_read(int fd, char *buf, int len)
{
char *ptr = buf;
int resid = len;
int bytes_read = 0;
while (resid > 0) {
errno = 0;
bytes_read = read(fd, ptr, resid);
if (bytes_read < 0) {
if (errno == EINTR)
continue;
else
return (-1);
}
resid -= bytes_read;
ptr += bytes_read;
}
return (len - resid);
}
static int
vs_icap_send_chunk(vs_scan_ctx_t *ctx, int chunk_len)
{
char *hdr = ctx->vsc_info.vsi_send_hdr;
char *dbuf = ctx->vsc_info.vsi_send_buf;
char *tail;
char head[VS_HDR_SZ + 1];
int nread = 0, hlen, tlen = 2;
if (chunk_len > VS_BUF_SZ)
chunk_len = VS_BUF_SZ;
if ((nread = vs_icap_read(ctx->vsc_fd, dbuf, chunk_len)) < 0)
return (-1);
if (nread > 0) {
hlen = snprintf(head, sizeof (head), "%x\r\n", nread);
hdr += (VS_HDR_SZ - hlen);
(void) memcpy(hdr, head, hlen);
tail = dbuf + nread;
tail[0] = '\r';
tail[1] = '\n';
if (vs_icap_write(ctx->vsc_sockfd, hdr,
hlen + nread + tlen) < 0) {
return (-1);
}
}
return (nread);
}
static int
vs_icap_send_termination(vs_scan_ctx_t *ctx)
{
if (vs_icap_write(ctx->vsc_sockfd, VS_TERMINATION,
strlen(VS_TERMINATION)) < 0) {
return (-1);
}
return (0);
}
static int
vs_icap_readline(vs_scan_ctx_t *ctx, char *buf, int buflen)
{
char c;
int i, retval;
i = 0;
for (;;) {
errno = 0;
retval = recv(ctx->vsc_sockfd, &c, 1, 0);
if (retval < 0 && errno == EINTR)
continue;
if (retval <= 0) {
if (vscand_get_state() != VS_STATE_SHUTDOWN) {
syslog(LOG_ERR, "Error receiving data from "
"Scan Engine: %s", strerror(errno));
}
return (-1);
}
buf[i++] = c;
if (c == '\n')
break;
if (i >= (buflen - 2))
return (-1);
}
buf[i] = '\0';
vs_icap_trimspace(buf);
return (i);
}
static void
vs_icap_parse_hdrs(char delimiter, char *line, char **name, char **val)
{
char *q = line;
int line_len;
while (*q == ' ')
q++;
*name = q;
*val = 0;
if ((line_len = strlen(line)) == 0)
return;
if ((q = strchr(line, delimiter)) != 0) {
*q++ = '\0';
} else {
q = line + line_len;
}
while (*q == ' ')
q++;
*val = q;
}
static int
vs_icap_resp_violations(vs_scan_ctx_t *ctx, int hdr_id, char *line)
{
int i, rv, vcnt;
(void) sscanf(line, "%d", &vcnt);
ctx->vsc_result->vsr_nviolations =
(vcnt > VS_MAX_VIOLATIONS) ? VS_MAX_VIOLATIONS : vcnt;
ctx->vsc_info.vsi_threat_hdr = VS_RESP_X_VIOLATIONS;
for (i = 0; i < vcnt; i++) {
if ((rv = vs_icap_resp_violation_rec(ctx, i)) < 0)
return (rv);
}
return (1);
}
static int
vs_icap_resp_violation_rec(vs_scan_ctx_t *ctx, int vr_idx)
{
int vline;
int retval = 0;
char *buf = ctx->vsc_info.vsi_recv_buf;
vs_vrec_t *vr;
if (vr_idx < VS_MAX_VIOLATIONS) {
vr = &ctx->vsc_result->vsr_vrec[vr_idx];
} else {
vr = 0;
}
for (vline = 0; vline < VS_VIOLATION_LINES; vline++) {
if ((retval = vs_icap_readline(ctx, buf, VS_BUF_SZ)) < 0)
return (-1);
if ((retval == 0) || (!buf[0]))
break;
if (vr) {
switch (vline) {
case 0:
break;
case 1:
(void) strlcpy(vr->vr_desc, buf,
VS_DESCRIPTION_MAX);
break;
case 2:
(void) sscanf(buf, "%d", &vr->vr_id);
break;
case 3:
(void) sscanf(buf, "%d", &vr->vr_res);
break;
}
}
}
return (1);
}
static int
vs_icap_opt_value(vs_scan_ctx_t *ctx, int hdr_id, char *line)
{
int x;
long val;
char *end;
switch (hdr_id) {
case VS_OPT_PREVIEW:
(void) sscanf(line, "%d", &x);
if (x < VS_MIN_PREVIEW_LEN)
x = VS_MIN_PREVIEW_LEN;
if (x > VS_BUF_SZ)
x = VS_BUF_SZ;
ctx->vsc_options.vso_preview_len = x;
break;
case VS_OPT_TTL:
if (*line == 0) {
ctx->vsc_options.vso_req_time = -1;
break;
}
val = strtol(line, &end, 10);
if ((end != (line + strlen(line))) || (val < 0))
break;
ctx->vsc_options.vso_ttl = val;
ctx->vsc_options.vso_req_time = time(0);
break;
case VS_OPT_ALLOW:
(void) sscanf(line, "%d", &ctx->vsc_options.vso_allow);
break;
case VS_OPT_SERVICE:
(void) strlcpy(ctx->vsc_options.vso_service, line,
VS_SERVICE_SZ);
break;
case VS_OPT_X_DEF_INFO:
(void) strlcpy(ctx->vsc_options.vso_defninfo, line,
VS_DEFN_SZ);
break;
case VS_OPT_METHODS:
if (strstr(line, "RESPMOD") != NULL)
ctx->vsc_options.vso_respmod = 1;
break;
case VS_OPT_ISTAG:
vs_icap_istag_to_scanstamp(line,
ctx->vsc_options.vso_scanstamp);
break;
default:
break;
}
return (1);
}
static int
vs_icap_resp_istag(vs_scan_ctx_t *ctx, int hdr_id, char *line)
{
vs_icap_istag_to_scanstamp(line, ctx->vsc_result->vsr_scanstamp);
(void) pthread_mutex_lock(&vs_opt_mutex);
if (vs_icap_compare_se(ctx->vsc_idx,
ctx->vsc_host, ctx->vsc_port) == 0) {
if (strcmp(vs_options[ctx->vsc_idx].vso_scanstamp,
ctx->vsc_result->vsr_scanstamp) != 0) {
(void) strlcpy(vs_options[ctx->vsc_idx].vso_scanstamp,
ctx->vsc_result->vsr_scanstamp,
sizeof (vs_scanstamp_t));
}
}
(void) pthread_mutex_unlock(&vs_opt_mutex);
return (1);
}
static void
vs_icap_istag_to_scanstamp(char *istag, vs_scanstamp_t scanstamp)
{
char *p = istag;
int len;
if (p[0] == '"')
++p;
len = strlen(p);
if (p[len - 1] == '"')
--len;
if (len < sizeof (vs_scanstamp_t))
(void) strlcpy(scanstamp, p, len + 1);
}
static int
vs_icap_opt_ext(vs_scan_ctx_t *ctx, int hdr_id, char *line)
{
vs_options_t *opt = &ctx->vsc_options;
switch (hdr_id) {
case VS_OPT_XFER_PREVIEW:
if (opt->vso_xfer_preview) {
free(opt->vso_xfer_preview);
opt->vso_xfer_preview = 0;
}
if (strstr(line, "*")) {
opt->vso_xfer_how = VS_PREVIEW_ALL;
} else {
opt->vso_xfer_preview = vs_icap_make_strvec
(line, EXT_SEPARATOR);
opt->vso_xfer_how = VS_PREVIEW_LIST;
}
break;
case VS_OPT_XFER_COMPLETE :
if (opt->vso_xfer_complete) {
free(opt->vso_xfer_complete);
opt->vso_xfer_complete = 0;
}
if (strstr(line, "*")) {
opt->vso_xfer_how = VS_PREVIEW_NONE;
} else {
opt->vso_xfer_complete = vs_icap_make_strvec
(line, EXT_SEPARATOR);
opt->vso_xfer_how = VS_PREVIEW_EXCEPT;
}
break;
default:
break;
}
return (1);
}
static int
vs_icap_resp_infection(vs_scan_ctx_t *ctx, int hdr_id, char *line)
{
char *name, *val;
int i, got = 0;
int type = 0, res = 0;
char *desc = 0;
vs_vrec_t *vr = 0;
for (i = 0; i < VS_INFECTION_FIELDS; i++) {
vs_icap_parse_hdrs('=', line, &name, &val);
switch (i) {
case 0:
if (MATCH(name, "Type")) {
(void) sscanf(val, "%d", &type);
got++;
}
break;
case 1:
if (MATCH(name, "Resolution")) {
(void) sscanf(val, "%d", &res);
got++;
}
break;
case 2:
if (MATCH(name, "Threat")) {
desc = val;
got++;
}
break;
default :
break;
}
if ((line = strstr(val, ";")))
line++;
}
if (got != VS_INFECTION_FIELDS)
return (0);
if ((ctx->vsc_result->vsr_nviolations == 0) ||
(ctx->vsc_info.vsi_threat_hdr < VS_RESP_X_INFECTION)) {
vr = &ctx->vsc_result->vsr_vrec[0];
vr->vr_id = type;
vr->vr_res = res;
(void) strlcpy(vr->vr_desc, desc, VS_DESCRIPTION_MAX);
ctx->vsc_result->vsr_nviolations = 1;
ctx->vsc_info.vsi_threat_hdr = VS_RESP_X_INFECTION;
}
return (1);
}
static int
vs_icap_resp_virus_id(vs_scan_ctx_t *ctx, int hdr_id, char *line)
{
vs_vrec_t *vr = 0;
if (ctx->vsc_result->vsr_nviolations == 0) {
vr = &ctx->vsc_result->vsr_vrec[0];
vr->vr_id = 0;
vr->vr_res = 0;
(void) strlcpy(vr->vr_desc, line, VS_DESCRIPTION_MAX);
ctx->vsc_result->vsr_nviolations = 1;
ctx->vsc_info.vsi_threat_hdr = VS_RESP_X_VIRUS_ID;
}
return (1);
}
static int
vs_icap_resp_encap(vs_scan_ctx_t *ctx, int hdr_id, char *line)
{
if (strstr(line, "res-hdr"))
ctx->vsc_info.vsi_res_hdr = B_TRUE;
if (strstr(line, "res-body"))
ctx->vsc_info.vsi_res_body = B_TRUE;
return (1);
}
int
vs_icap_compare_scanstamp(int idx, vs_scanstamp_t scanstamp)
{
int rc;
if (!scanstamp || scanstamp[0] == '\0')
return (-1);
(void) pthread_mutex_lock(&vs_opt_mutex);
rc = strcmp(scanstamp, vs_options[idx].vso_scanstamp);
(void) pthread_mutex_unlock(&vs_opt_mutex);
return (rc);
}
static int
vs_icap_compare_se(int idx, char *host, int port)
{
if (vs_options[idx].vso_port != port)
return (-1);
if (strcmp(vs_options[idx].vso_host, host) != 0)
return (-1);
return (0);
}
static void
vs_icap_free_options(vs_options_t *options)
{
if (options->vso_xfer_preview)
free(options->vso_xfer_preview);
if (options->vso_xfer_complete)
free(options->vso_xfer_complete);
(void) memset(options, 0, sizeof (vs_options_t));
}
void
vs_icap_copy_options(vs_options_t *to_opt, vs_options_t *from_opt)
{
*to_opt = *from_opt;
if (from_opt->vso_xfer_preview) {
to_opt->vso_xfer_preview =
vs_icap_copy_strvec(from_opt->vso_xfer_preview);
}
if (from_opt->vso_xfer_complete) {
to_opt->vso_xfer_complete =
vs_icap_copy_strvec(from_opt->vso_xfer_complete);
}
}
static void
vs_icap_update_options(vs_scan_ctx_t *ctx)
{
int idx = ctx->vsc_idx;
(void) pthread_mutex_lock(&vs_opt_mutex);
if (vs_icap_compare_se(idx, ctx->vsc_host, ctx->vsc_port) == 0) {
vs_icap_free_options(&vs_options[idx]);
vs_icap_copy_options(&vs_options[idx], &ctx->vsc_options);
}
(void) pthread_mutex_unlock(&vs_opt_mutex);
}
static iovec_t *
vs_icap_make_strvec(char *line, const char *sep)
{
iovec_t *vec;
char *tmp, *ctx;
int datalen, len;
datalen = strlen(line) + 1;
len = sizeof (iovec_t) + datalen;
if ((vec = (iovec_t *)calloc(1, len)) == 0)
return (0);
vec->iov_len = len;
vec->iov_base = (char *)vec + sizeof (iovec_t);
(void) strlcpy(vec->iov_base, line, datalen);
for (tmp = strtok_r(vec->iov_base, sep, &ctx); tmp;
tmp = strtok_r(0, sep, &ctx)) {
}
return (vec);
}
static iovec_t *
vs_icap_copy_strvec(iovec_t *from_vec)
{
iovec_t *to_vec;
if ((to_vec = (iovec_t *)calloc(1, from_vec->iov_len)) == 0)
return (0);
bcopy(from_vec, to_vec, from_vec->iov_len);
to_vec->iov_base = (char *)to_vec + sizeof (iovec_t);
return (to_vec);
}
static int
vs_icap_check_ext(char *ext, iovec_t *vec)
{
char *p, *end = (char *)vec + vec->iov_len;
for (p = vec->iov_base; p < end; p += strlen(p) + 1) {
if (MATCH(ext, p))
return (1);
}
return (0);
}
static char *
vs_icap_resp_str(int rc)
{
vs_resp_msg_t *p = icap_resp;
if (rc < 0)
rc = -rc;
while (p->vsm_rc != VS_RESP_UNKNOWN) {
if (p->vsm_rc == rc)
break;
p++;
}
return (p->vsm_msg);
}
static void
vs_icap_trimspace(char *buf)
{
char *p = buf;
char *q = buf;
if (buf == 0)
return;
while (*p && isspace(*p))
++p;
while ((*q = *p++) != 0)
++q;
if (q != buf) {
while ((--q, isspace(*q)) != 0)
*q = '\0';
}
}
static int
vs_icap_uri_encode(char *buf, int size, char *data)
{
unsigned char *iptr;
char *optr = buf;
int len = strlen(data);
for (iptr = (unsigned char *)data; *iptr; iptr++) {
if (vs_icap_uri_illegal_char(*iptr)) {
if ((len += 2) >= size)
return (-1);
(void) sprintf(optr, "%%%0x", *iptr);
optr += 3;
} else {
if (len >= size)
return (-1);
*optr++ = *iptr;
}
}
*optr = '\0';
return (len);
}
static int
vs_icap_uri_illegal_char(char c)
{
static const char *uri_illegal_chars = "<>#%\" {}|\\^[]`";
if ((c <= 0x1F) || (c >= 0x7F))
return (1);
if (strchr(uri_illegal_chars, c))
return (1);
return (0);
}