/*
* CDDL HEADER START
*
* The contents of this file are subject to the terms of the
* Common Development and Distribution License (the "License").
* You may not use this file except in compliance with the License.
*
* You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
* or http://www.opensolaris.org/os/licensing.
* See the License for the specific language governing permissions
* and limitations under the License.
*
* When distributing Covered Code, include this CDDL HEADER in each
* file and include the License file at usr/src/OPENSOLARIS.LICENSE.
* If applicable, add the following below this CDDL HEADER, with the
* fields enclosed by brackets "[]" replaced with your own identifying
* information: Portions Copyright [yyyy] [name of copyright owner]
*
* CDDL HEADER END
*/
/*
* Copyright 2009 Sun Microsystems, Inc. All rights reserved.
* Use is subject to license terms.
*/
/*
* Description: Module contains supporting functions used by functions
* defined in vs_svc.c. It also contains some internal(static) functions.
*/
#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"
/* prototypes of local functions */
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);
/* process options and respmod headers */
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);
/* Utility functions for handling OPTIONS data: vs_options_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 *);
/* icap response message */
static char *vs_icap_resp_str(int);
/*
* local variables
*/
/* option headers - and handler functions */
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}
};
/* resp hdrs - and handler functions */
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}
};
/* ICAP response code to string mappings */
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;
/*
* vs_icap_init
* initialization performed when daemon is loaded
*/
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);
}
/*
* vs_icap_fini
* cleanup performed when daemon is unloaded
*/
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);
}
/*
* vs_icap_config
*
* When a new VSCAN configuration is specified, this will be
* called per scan engine. If the scan engine host or port has
* changed delete the vs_options entry for that scan engine.
*/
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);
}
/*
* vs_icap_scan_file
*
* Create a context (vs_scan_ctx_t) for the scan operation and initialize
* its options info. If the scan engine connection's IP or port is different
* from that held in vs_options the vs_options info is old and should
* be deleted (vs_icap_free_options). Otherwise, copy the vs_options info
* into the context.
* file name, size and decsriptor are also copied into the context
*
* Handle the ICAP protocol communication with the external Scan Engine to
* perform the scan
* - send an OPTIONS request if necessary
* - send RESPMOD scan request
* - process the response and save any cleaned data to file
*
* Returns: result->vsr_rc
*/
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);
/* retry once on ENOENT as /dev link may not be created yet */
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);
}
/* initialize context */
(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;
/* Hooks for future saving of repaired data, not yet in use */
ctx.vsc_flags |= VS_NO_REPAIR;
ctx.vsc_repair = 0;
ctx.vsc_repair_fname = NULL;
ctx.vsc_repair_fd = -1;
/* take a copy of vs_options[idx] if they match the SE specified */
(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);
/*
* default the result to scan engine error.
* Any non scan-engine errors will reset it to VS_RESULT_ERROR
*/
result->vsr_rc = VS_RESULT_SE_ERROR;
/* do the scan */
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);
}
/* ********************************************************************* */
/* Local Function definitions */
/* ********************************************************************* */
/*
* vs_icap_option_request
*
* Send ICAP options message and await/process the response.
*
* The ICAP options request needs to be sent when a connection
* is first made with the scan engine. Unless the scan engine
* determines that the options will never expire (which we save
* as optione_req_time == -1) the request should be resent after
* the expiry time specified by the icap server.
*
* Returns: 0 - success
* -1 - error
*/
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);
}
/*
* vs_icap_send_option_req
*
* Send an OPTIONS request to the scan engine
* The Symantec ICAP server REQUIRES the resource name (VS_SERVICE_NAME)
* after the IP address, otherwise it closes the connection.
*
* Returns: 0 - success
* -1 - error
*/
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) {
/* non SE error */
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);
}
/*
* vs_icap_read_option_resp
*
* Returns: 0 - success
* -1 - error
*/
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);
}
/*
* vs_icap_respmod_request
*
* Send respmod request and receive and process ICAP response.
* Preview:
* ICAP allows for an optional "preview" request. In the option negotiation,
* the server may ask for a list of types to be previewed, or to be sent
* complete (no preview).
* This is advisory. It is ok to skip the preview step, as done when the file
* is smaller than the preview_len.
* Process Response:
* - read and parse the RESPMOD response headers
* - populate the result structure
* - read any encapsulated response headers
* - read any encapsulated response body and, if it represents cleaned
* file data, overwrite the file with it
*
* Returns: 0 - success
* -1 - error
*/
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 > block (VS_BUF_SZ) remains, re-align to block boundary */
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);
}
/* Send the remainder of the file... */
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);
/* sending of ICAP request complete */
if (vs_icap_read_respmod_resp(ctx) < 0)
return (-1);
return (0);
}
/*
* vs_icap_may_preview
*
* Returns: 1 - preview
* 0 - don't preview
*/
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 the file is smaller than the preview size, don't preview */
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:
/* Preview everything except types in xfer_complete */
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:
/* Preview only types in the the xfer_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);
}
/*
* vs_icap_find_ext
*
* Returns: ptr to file's extension in fname
* 0 if no extension
*/
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;
}
/* Get file extension */
if ((ext_str = strrchr(last_comp, '.')) != 0) {
ext_str++;
if (strlen(ext_str) == 0)
ext_str = 0;
}
return (ext_str);
}
/*
* vs_icap_send_preview
*
* Returns: bytes sent (preview + alignment)
* -1 - error
*/
static int
vs_icap_send_preview(vs_scan_ctx_t *ctx)
{
int preview_len = ctx->vsc_options.vso_preview_len;
int bytes_sent;
/* Send a RESPMOD request with "preview" mode. */
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);
}
/*
* vs_icap_send_respmod_hdr
*
* Create and send the RESPMOD request headers to the scan engine.
*
* Returns: 0 success
* < 0 error
*/
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) {
/* non SE error */
ctx->vsc_result->vsr_rc = VS_RESULT_ERROR;
return (-1);
}
/* send the headers */
if (vs_icap_write(ctx->vsc_sockfd,
ctx->vsc_info.vsi_send_buf, len) < 0) {
return (-1);
}
return (0);
}
/*
* vs_icap_create_respmod_hdr
*
* Create the RESPMOD request headers.
* - RESPMOD, Host, Allow, [Preview], Encapsulated, encapsulated request hdr,
* encapsulated response hdr
* Encapsulated data is sent separately subsequent to vs_icap_send_respmod_hdr,
* via calls to vs_icap_send_chunk.
*
* The Symantec ICAP server REQUIRES the resource name (VS_SERVICE_NAME)
* after the IP address, otherwise it closes the connection.
*
* Returns: -1 error
* length of headers data
*/
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) {
/* non SE error */
ctx->vsc_result->vsr_rc = VS_RESULT_ERROR;
return (-1);
}
(void) memset(hbufp, 0, hbufsp);
/* First the ICAP "request" part. (at offset 0) */
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;
}
/* Reserve space to later insert encapsulation offsets, & blank line */
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;
/* "offset zero" for the encapsulated parts that follow */
encap_off0 = hbufp;
/* Encapsulated request header (req_hdr) & blank line */
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;
/* Encapsulated response header (res_hdr) & blank line */
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;
/* response body section - res-body ("chunked data") */
res_body = hbufp;
/* Insert offsets in encap_hdr */
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);
/* undo the null from snprintf */
encap_hdr[tlen] = ' ';
/* return length */
return (hbufp - hbuf0);
}
/*
* vs_icap_read_respmod_resp
*
* Used for both preview and final RESMOD response
*/
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) {
/* A VS_RESP_CONTINUE should not have encapsulated data */
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);
}
/*
* vs_icap_read_resp_code
*
* Get the response code from the icap response messages
*/
static int
vs_icap_read_resp_code(vs_scan_ctx_t *ctx)
{
char *buf = ctx->vsc_info.vsi_recv_buf;
int retval;
/* Break on error or non-blank line. */
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);
}
}
}
/*
* vs_icap_read_hdr
*
* Reads all response headers.
* As each line is read it is parsed and passed to the appropriate handler.
*
* Returns: 0 - success
* -1 - error
*/
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;
/* Break on error or blank line. */
for (;;) {
(void) memset(buf, '\0', VS_BUF_SZ);
if ((retval = vs_icap_readline(ctx, buf, VS_BUF_SZ)) < 0)
return (-1);
/* Empty line (CR/LF) normal break */
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);
}
/*
* vs_icap_set_scan_result
*
* Sets the vs_result_t vsr_rc from the icap_resp_code and
* any violation information in vs_result_t
*
* Returns: 0 - success
* -1 - error
*/
static int
vs_icap_set_scan_result(vs_scan_ctx_t *ctx)
{
int i;
vs_result_t *result = ctx->vsc_result;
if (!result->vsr_scanstamp)
(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 we have no violations , that means all ok */
if (result->vsr_nviolations == 0) {
result->vsr_rc = VS_RESULT_CLEAN;
break;
}
/* Any infections not repaired? */
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 :
/* file is repaired */
result->vsr_rc = VS_RESULT_CLEANED;
break;
case VS_RESP_FORBIDDEN:
/* file is infected and could not be repaired */
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);
}
/*
* vs_icap_read_encap_hdr
*
* Read the encapsulated response header to determine the length of
* encapsulated data and, in some cases, to detect the infected state
* of the file.
*
* Use of http response code:
* Trend IWSS does not return virus information in the RESPMOD response
* headers unless the OPTIONAL "include X_Infection_Found" checkbox is
* checked and "disable_infected_url_block=yes" is set in intscan.ini.
* Thus if we haven't already detected the infected/cleaned status
* (ie if vsr_rc == VS_RESULT_CLEAN) we attempt to detect the
* infected/cleaned state of a file from a combination of the ICAP and
* http resp codes.
* Here are the response code values that Trend IWSS returns:
* - clean: icap resp = VS_RESP_NO_CONT_NEEDED
* - quarantine: icap resp = VS_RESP_OK, http resp = VS_RESP_FORBIDDEN
* - cleaned: icap resp = VS_RESP_OK, http resp = VS_RESP_OK
* For all other vendors' scan engines (so far) the infected/cleaned
* state of the file has already been detected from the RESPMOD
* response headers.
*/
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;
/* Break on error or blank line. */
for (;;) {
if ((retval = vs_icap_readline(ctx, buf, VS_BUF_SZ)) < 0)
return (-1);
/* Empty line (CR/LF) normal break */
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 not yet detected infection, interpret http_rc */
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);
}
/*
* vs_icap_read_encap_data
*
* Read the encapsulated response data.
*
* If the response data represents cleaned file data (for an infected file)
* and VS_NO_REPAIR is not set, open repair file to save the reponse body
* data in. Set the repair flag in the scan context. The repair flag is used
* during the processing of the response data. If the flag is set then the
* data is written to file. If any error occurs which invalidates the repaired
* data file the repair flag gets reset to 0, and the data will be discarded.
*
* The result is reset to VS_RESULT_FORBIDDEN until all of the cleaned data
* has been successfully received and processed. It is then reset to
* VS_RESULT_CLEANED.
*
* If the data doesn't represent cleaned file data, or we cannot (or don't
* want to) write the cleaned data to file, the data is discarded (repair flag
* in ctx == 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;
}
}
/*
* vs_icap_read_resp_body handles errors internally;
* resets ctx->vsc_repair
*/
(void) vs_icap_read_resp_body(ctx);
if (ctx->vsc_repair_fd != -1) {
(void) close(ctx->vsc_repair_fd);
if (ctx->vsc_repair) {
/* repair file contains the cleaned data */
ctx->vsc_result->vsr_rc = VS_RESULT_CLEANED;
} else {
/* error occured processing data. Remove repair file */
(void) unlink(ctx->vsc_repair_fname);
}
}
}
/*
* vs_icap_create_repair_file
*
* Create and open a file to save cleaned data in.
*/
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);
}
/*
* vs_icap_read_resp_body
*
* Repeatedly call vs_icap_read_body_chunk until it returns:
* 0 indicating that there's no more data to read or
* -1 indicating a read error -> reset ctx->vsc_repair 0
*
* Returns: 0 success
* -1 error
*/
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);
}
/*
* vs_icap_read_body_chunk
*
* Read the chunk size, then read the chunk of data and write the
* data to file repair_fd (or discard it).
* If the data cannot be successfully written to file, set repair
* flag in ctx to 0, and discard all subsequent data.
*
* Returns: chunk size
* -1 on error
*/
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;
/* Read and parse the chunk size. */
if ((vs_icap_readline(ctx, lbuf, VS_BUF_SZ) < 0) ||
(!sscanf(lbuf, "%x", &chunk_size))) {
return (-1);
}
/* Read and save/discard chunk */
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;
}
/* Eat one CR/LF after the data */
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);
}
/* *********************************************************************** */
/* Utility read, write functions */
/* *********************************************************************** */
/*
* vs_icap_write
*
* Return: 0 if all data successfully written
* -1 otherwise
*/
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);
}
/*
* vs_icap_read
*
* Returns: bytes_read (== len unless EOF hit before len bytes read)
* -1 error
*/
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);
}
/*
* vs_icap_send_chunk
*
* Send a "chunk" of file data, containing:
* - Length (in hex) CR/NL
* - [optiona data]
* - CR/NL
*
* Returns: data length sent (not including encapsulation)
* -1 - error
*/
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;
/* Read the data. */
if ((nread = vs_icap_read(ctx->vsc_fd, dbuf, chunk_len)) < 0)
return (-1);
if (nread > 0) {
/* wrap data in a header and trailer */
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);
}
/*
* vs_icap_send_termination
*
* Send 0 length termination to scan engine: "0\r\n\r\n"
*
* Returns: 0 - success
* -1 - error
*/
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);
}
/*
* vs_icap_readline
*
* Read a line of response data from the socket. \n indicates end of line.
*
* Returns: bytes read
* -1 - error
*/
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';
/* remove preceding and trailing whitespace */
vs_icap_trimspace(buf);
return (i);
}
/* ************************************************************************ */
/* HEADER processing */
/* ************************************************************************ */
/*
* vs_icap_parse_hdrs
*
* parse an icap hdr line to find name and value
*/
static void
vs_icap_parse_hdrs(char delimiter, char *line, char **name, char **val)
{
char *q = line;
int line_len;
/* strip any spaces */
while (*q == ' ')
q++;
*name = q;
*val = 0;
/* Empty line is normal termination */
if ((line_len = strlen(line)) == 0)
return;
if ((q = strchr(line, delimiter)) != 0) {
*q++ = '\0';
} else {
q = line + line_len;
}
/* value part follows spaces */
while (*q == ' ')
q++;
*val = q;
}
/*
* vs_icap_resp_violations
*/
/*ARGSUSED*/
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);
}
/*
* vs_icap_resp_violation_rec
*
* take all violation data (up to VS_MAX_VIOLATIONS) and save it
* in violation_info.
* each violation has 4 lines of info: doc name, virus name,
* virus id and resolution
*/
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);
/* empty line? */
if ((retval == 0) || (!buf[0]))
break;
if (vr) {
switch (vline) {
case 0: /* doc name */
break;
case 1: /* Threat Description */
(void) strlcpy(vr->vr_desc, buf,
VS_DESCRIPTION_MAX);
break;
case 2: /* Problem ID */
(void) sscanf(buf, "%d", &vr->vr_id);
break;
case 3: /* Resolution */
(void) sscanf(buf, "%d", &vr->vr_res);
break;
}
}
}
return (1);
}
/*
* vs_icap_opt_value
* given an icap options hdr string, process value
*/
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);
}
/*
* vs_icap_resp_istag
*
* Called to handle ISTAG when received in RESPMOD response.
* - populate result->vsr_scanstamp from istag
* - update the scanstamp in vs_options and log the update.
*/
/*ARGSUSED*/
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);
/* update the scanstamp in vs_options */
(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);
}
/*
* vs_icap_istag_to_scanstamp
*
* Copies istag into scanstamp, stripping leading and trailing
* quotes '"' from istag. If the istag is invalid (too long)
* scanstamp will be left unchanged.
*
* vs_scanstamp_t is defined to be large enough to hold the
* istag plus a null terminator.
*/
static void
vs_icap_istag_to_scanstamp(char *istag, vs_scanstamp_t scanstamp)
{
char *p = istag;
int len;
/* eliminate preceding '"' */
if (p[0] == '"')
++p;
/* eliminate trailing '"' */
len = strlen(p);
if (p[len - 1] == '"')
--len;
if (len < sizeof (vs_scanstamp_t))
(void) strlcpy(scanstamp, p, len + 1);
}
/*
* vs_icap_opt_ext
*
* read the transfer preview / transfer complete headers to
* determine which file types can be previewed
*/
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);
}
/*
* vs_icap_resp_infection
*
* read the type, resolution and threat description for each
* reported violation and save in ctx->vsc_result
*/
/*ARGSUSED*/
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);
/*
* We may have info from an X-Violations-Found record, (which provides
* more complete information). If so, don't destroy what we have.
*/
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);
}
/*
* vs_icap_resp_virus_id
*
* X-Virus-ID is defined as being a shorter alternative to X-Infection-Found.
* If we already have virus information, from either X-Infection-Found or
* X-Violations-Found, it will be more complete, so don't overwrite it with
* the info from X-Virus-ID.
*/
/*ARGSUSED*/
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);
}
/*
* vs_icap_resp_encap
*
* get the encapsulated header info
*/
/*ARGSUSED*/
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);
}
/*
* Utility functions for handling OPTIONS data: vs_options_t
*/
/*
* vs_icap_compare_scanstamp
* compare scanstamp with that stored for engine idx
*
* Returns: 0 - if equal
*/
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);
}
/*
* vs_icap_compare_se
* compare host and port with that stored for engine idx
*
* Returns: 0 - if equal
*/
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);
}
/*
* vs_icap_free_options
*
* Free dynamic parts of vs_options_t: xfer_preview, xfer_complete
*/
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));
}
/*
* vs_icap_copy_options
*/
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);
}
}
/*
* vs_icap_update_options
*/
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);
}
/*
* vs_icap_make_strvec
*
* Populate a iovec_t from line, where line is a string of 'sep'
* separated fields. Within the copy of line in the iovec_t each
* field will be null terminated with leading & trailing whitespace
* removed. This allows for fast searching.
*
* The iovec_t itself and the data it points to are allocated
* as a single chunk.
*/
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);
/* tokenize data for easier searching */
for (tmp = strtok_r(vec->iov_base, sep, &ctx); tmp;
tmp = strtok_r(0, sep, &ctx)) {
}
return (vec);
}
/*
* vs_icap_copy_strvec
*
* allocate and copy strvec
*/
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);
}
/*
* vs_icap_check_ext
*
* Returns: 1 - if ext in strvec
* 0 - otherwise
*/
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);
}
/*
* vs_icap_resp_str
*/
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);
}
/*
* vs_icap_trimspace
*
* Trims whitespace from both the beginning and end of a string. This
* function alters the string buffer in-place.
*
* Whitespaces found at the beginning of the string are eliminated by
* moving forward the start of the string at the first non-whitespace
* character.
* Whitespace found at the end of the string are overwritten with nulls.
*
*/
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';
}
}
/*
* vs_icap_uri_encode
*
* Encode uri data (eg filename) in accordance with RFC 2396
* 'Illegal' characters should be replaced with %hh, where hh is
* the hex value of the character. For example a space would be
* replaced with %20.
* Filenames are all already UTF-8 encoded. Any UTF-8 octects that
* are 'illegal' characters will be encoded as described above.
*
* Paramaters: data - string to be encoded (NULL terminated)
* buf - output buffer (NULL terminated)
* size - size of output buffer
*
* Returns: strlen of encoded data on success
* -1 size on error (contents of buf undefined)
*/
static int
vs_icap_uri_encode(char *buf, int size, char *data)
{
unsigned char *iptr;
char *optr = buf;
int len = strlen(data);
/* modify the 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);
}
/*
* vs_icap_uri_illegal_char
*
* The following us-ascii characters (UTF-8 octets) are 'illegal':
* < > # % " { } | \ ^ [ ] ` space, 0x01 -> 0x1F & 0x7F
* All non us-ascii UTF-8 octets ( >= 0x80) are illegal.
*
* Returns: 1 if character is not allowed in a URI
* 0 otherwise
*/
static int
vs_icap_uri_illegal_char(char c)
{
static const char *uri_illegal_chars = "<>#%\" {}|\\^[]`";
/* us-ascii non printable characters or non us-ascii */
if ((c <= 0x1F) || (c >= 0x7F))
return (1);
/* us-ascii dis-allowed characters */
if (strchr(uri_illegal_chars, c))
return (1);
return (0);
}