root/usr/src/cmd/vscan/vscand/vs_icap.c
/*
 * 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;

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

}