Home | History | Annotate | Download | only in vscand
      1 /*
      2  * CDDL HEADER START
      3  *
      4  * The contents of this file are subject to the terms of the
      5  * Common Development and Distribution License (the "License").
      6  * You may not use this file except in compliance with the License.
      7  *
      8  * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
      9  * or http://www.opensolaris.org/os/licensing.
     10  * See the License for the specific language governing permissions
     11  * and limitations under the License.
     12  *
     13  * When distributing Covered Code, include this CDDL HEADER in each
     14  * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
     15  * If applicable, add the following below this CDDL HEADER, with the
     16  * fields enclosed by brackets "[]" replaced with your own identifying
     17  * information: Portions Copyright [yyyy] [name of copyright owner]
     18  *
     19  * CDDL HEADER END
     20  */
     21 /*
     22  * Copyright 2009 Sun Microsystems, Inc.  All rights reserved.
     23  * Use is subject to license terms.
     24  */
     25 
     26 /*
     27  * Description:  Module contains supporting functions used by functions
     28  * defined in vs_svc.c. It also contains some internal(static) functions.
     29  */
     30 
     31 #include <stdarg.h>
     32 #include <stdio.h>
     33 #include <stdlib.h>
     34 #include <unistd.h>
     35 #include <errno.h>
     36 #include <time.h>
     37 #include <fcntl.h>
     38 #include <syslog.h>
     39 #include <ctype.h>
     40 #include <strings.h>
     41 #include <string.h>
     42 #include <limits.h>
     43 #include <pthread.h>
     44 #include <sys/types.h>
     45 #include <sys/socket.h>
     46 #include <sys/debug.h>
     47 #include <netinet/in.h>
     48 #include <arpa/inet.h>
     49 
     50 #include "vs_incl.h"
     51 #include "vs_icap.h"
     52 
     53 /*  prototypes of local functions  */
     54 static int  vs_icap_option_request(vs_scan_ctx_t *);
     55 static int  vs_icap_send_option_req(vs_scan_ctx_t *);
     56 static int  vs_icap_read_option_resp(vs_scan_ctx_t *);
     57 
     58 static int  vs_icap_respmod_request(vs_scan_ctx_t *);
     59 static int  vs_icap_may_preview(vs_scan_ctx_t *);
     60 static char *vs_icap_find_ext(char *);
     61 static int  vs_icap_send_preview(vs_scan_ctx_t *);
     62 static int  vs_icap_send_respmod_hdr(vs_scan_ctx_t *, int);
     63 static int  vs_icap_create_respmod_hdr(vs_scan_ctx_t *, int);
     64 static int  vs_icap_uri_encode(char *, int, char *);
     65 static int  vs_icap_uri_illegal_char(char);
     66 
     67 static int  vs_icap_read_respmod_resp(vs_scan_ctx_t *);
     68 static int  vs_icap_read_resp_code(vs_scan_ctx_t *);
     69 static int  vs_icap_read_hdr(vs_scan_ctx_t *, vs_hdr_t *, int);
     70 
     71 static int  vs_icap_set_scan_result(vs_scan_ctx_t *);
     72 static int  vs_icap_read_encap_hdr(vs_scan_ctx_t *);
     73 static void vs_icap_read_encap_data(vs_scan_ctx_t *);
     74 static int  vs_icap_create_repair_file(vs_scan_ctx_t *);
     75 static int  vs_icap_read_resp_body(vs_scan_ctx_t *);
     76 static int  vs_icap_read_body_chunk(vs_scan_ctx_t *);
     77 
     78 static int  vs_icap_send_chunk(vs_scan_ctx_t *, int);
     79 static int  vs_icap_send_termination(vs_scan_ctx_t *);
     80 static int  vs_icap_readline(vs_scan_ctx_t *, char *, int);
     81 
     82 static int  vs_icap_write(int, char *, int);
     83 static int  vs_icap_read(int, char *, int);
     84 
     85 /* process options and respmod headers */
     86 static void vs_icap_parse_hdrs(char, char *, char **, char **);
     87 static int  vs_icap_opt_value(vs_scan_ctx_t *, int, char *);
     88 static int  vs_icap_opt_ext(vs_scan_ctx_t *, int, char *);
     89 static int  vs_icap_resp_violations(vs_scan_ctx_t *, int, char *);
     90 static int  vs_icap_resp_violation_rec(vs_scan_ctx_t *, int);
     91 static int  vs_icap_resp_infection(vs_scan_ctx_t *, int, char *);
     92 static int  vs_icap_resp_virus_id(vs_scan_ctx_t *, int, char *);
     93 static int  vs_icap_resp_encap(vs_scan_ctx_t *, int, char *);
     94 static int  vs_icap_resp_istag(vs_scan_ctx_t *, int, char *);
     95 static void vs_icap_istag_to_scanstamp(char *, vs_scanstamp_t);
     96 
     97 /* Utility functions for handling OPTIONS data: vs_options_t */
     98 static void vs_icap_free_options(vs_options_t *);
     99 static void vs_icap_copy_options(vs_options_t *, vs_options_t *);
    100 static void vs_icap_update_options(vs_scan_ctx_t *);
    101 static int vs_icap_compare_se(int, char *, int);
    102 
    103 static iovec_t *vs_icap_make_strvec(char *, const char *);
    104 static iovec_t *vs_icap_copy_strvec(iovec_t *);
    105 static int  vs_icap_check_ext(char *, iovec_t *);
    106 static void vs_icap_trimspace(char *);
    107 
    108 /* icap response message */
    109 static char *vs_icap_resp_str(int);
    110 
    111 /*
    112  * local variables
    113  */
    114 
    115 /* option headers  - and handler functions */
    116 vs_hdr_t option_hdrs[] = {
    117 	{ VS_OPT_SERVICE,	"Service",		vs_icap_opt_value},
    118 	{ VS_OPT_ISTAG,		"ISTag",		vs_icap_opt_value},
    119 	{ VS_OPT_METHODS,	"Methods",		vs_icap_opt_value},
    120 	{ VS_OPT_ALLOW,		"Allow",		vs_icap_opt_value},
    121 	{ VS_OPT_PREVIEW,	"Preview",		vs_icap_opt_value},
    122 	{ VS_OPT_XFER_PREVIEW,	"Transfer-Preview",	vs_icap_opt_ext},
    123 	{ VS_OPT_XFER_COMPLETE,	"Transfer-Complete",	vs_icap_opt_ext},
    124 	{ VS_OPT_MAX_CONNECTIONS, "Max-Connections",	vs_icap_opt_value},
    125 	{ VS_OPT_TTL,		"Options-TTL",		vs_icap_opt_value},
    126 	{ VS_OPT_X_DEF_INFO,	"X-Definition-Info",	vs_icap_opt_value}
    127 };
    128 
    129 
    130 /* resp hdrs  - and handler functions */
    131 vs_hdr_t resp_hdrs[] = {
    132 	{ VS_RESP_ENCAPSULATED,	"Encapsulated",	vs_icap_resp_encap},
    133 	{ VS_RESP_ISTAG,	"ISTag",	vs_icap_resp_istag},
    134 	{ VS_RESP_X_VIRUS_ID,	"X-Virus-ID",	vs_icap_resp_virus_id},
    135 	{ VS_RESP_X_INFECTION,	"X-Infection-Found",	vs_icap_resp_infection},
    136 	{ VS_RESP_X_VIOLATIONS,	"X-Violations-Found",	vs_icap_resp_violations}
    137 };
    138 
    139 /* ICAP response code to string mappings */
    140 vs_resp_msg_t icap_resp[] = {
    141 	{ VS_RESP_CONTINUE,		"Continue"},
    142 	{ VS_RESP_OK,			"OK"},
    143 	{ VS_RESP_CREATED,		"Virus Detected and Repaired"},
    144 	{ VS_RESP_NO_CONT_NEEDED,	"No Content Necessary"},
    145 	{ VS_RESP_BAD_REQ,		"Bad Request"},
    146 	{ VS_RESP_FORBIDDEN,		"File Infected and not repaired"},
    147 	{ VS_RESP_NOT_FOUND,		"URI not found"},
    148 	{ VS_RESP_NOT_ALLOWED,		"Method not allowed"},
    149 	{ VS_RESP_TIMEOUT,		"Request timedout"},
    150 	{ VS_RESP_INTERNAL_ERR,    	"Internal server error"},
    151 	{ VS_RESP_NOT_IMPL,		"Method not implemented"},
    152 	{ VS_RESP_SERV_UNAVAIL,    	"Service unavailable/overloaded"},
    153 	{ VS_RESP_ICAP_VER_UNSUPP,	"ICAP version not supported"},
    154 	{ VS_RESP_SCAN_ERR,		"Error scanning file"},
    155 	{ VS_RESP_NO_LICENSE,		"No AV License"},
    156 	{ VS_RESP_RES_UNAVAIL,		"Resource unavailable"},
    157 	{ VS_RESP_UNKNOWN,		"Unknown Error"},
    158 };
    159 
    160 static const char *EXT_SEPARATOR =  ",";
    161 static vs_options_t vs_options[VS_SE_MAX];
    162 static pthread_mutex_t vs_opt_mutex = PTHREAD_MUTEX_INITIALIZER;
    163 
    164 /*
    165  * vs_icap_init
    166  * initialization performed when daemon is loaded
    167  */
    168 void
    169 vs_icap_init()
    170 {
    171 
    172 	(void) pthread_mutex_lock(&vs_opt_mutex);
    173 	(void) memset(vs_options, 0, sizeof (vs_options_t));
    174 	(void) pthread_mutex_unlock(&vs_opt_mutex);
    175 }
    176 
    177 
    178 /*
    179  * vs_icap_fini
    180  * cleanup  performed when daemon is unloaded
    181  */
    182 void
    183 vs_icap_fini()
    184 {
    185 	int i;
    186 
    187 	(void) pthread_mutex_lock(&vs_opt_mutex);
    188 
    189 	for (i = 0; i < VS_SE_MAX; i++)
    190 		vs_icap_free_options(&vs_options[i]);
    191 
    192 	(void) pthread_mutex_unlock(&vs_opt_mutex);
    193 }
    194 
    195 
    196 /*
    197  * vs_icap_config
    198  *
    199  * When a new VSCAN configuration is specified, this will be
    200  * called per scan engine. If the scan engine host or port has
    201  * changed delete the vs_options entry for that scan engine.
    202  */
    203 void
    204 vs_icap_config(int idx, char *host, int port)
    205 {
    206 	(void) pthread_mutex_lock(&vs_opt_mutex);
    207 	if (vs_icap_compare_se(idx, host, port) != 0) {
    208 		vs_icap_free_options(&vs_options[idx]);
    209 		(void) strlcpy(vs_options[idx].vso_host, host,
    210 		    sizeof (vs_options[idx].vso_host));
    211 		vs_options[idx].vso_port = port;
    212 	}
    213 	(void) pthread_mutex_unlock(&vs_opt_mutex);
    214 }
    215 
    216 
    217 /*
    218  * vs_icap_scan_file
    219  *
    220  * Create a context (vs_scan_ctx_t) for the scan operation and initialize
    221  * its options info. If the scan engine connection's IP or port is different
    222  * from that held in vs_options the vs_options info is old and should
    223  * be deleted (vs_icap_free_options). Otherwise, copy the vs_options info
    224  * into the context.
    225  * file name, size and decsriptor are also copied into the context
    226  *
    227  * Handle the ICAP protocol communication with the external Scan Engine to
    228  * perform the scan
    229  *  - send an OPTIONS request if necessary
    230  *  - send RESPMOD scan request
    231  *  - process the response and save any cleaned data to file
    232  *
    233  * Returns: result->vsr_rc
    234  */
    235 int
    236 vs_icap_scan_file(vs_eng_ctx_t *eng, char *devname, char *fname,
    237     uint64_t fsize, int flags, vs_result_t *result)
    238 {
    239 	vs_scan_ctx_t ctx;
    240 	int fd;
    241 
    242 	fd = open(devname, O_RDONLY);
    243 
    244 	/* retry once on ENOENT as /dev link may not be created yet */
    245 	if ((fd == -1) && (errno == ENOENT)) {
    246 		(void) sleep(1);
    247 		fd = open(devname, O_RDONLY);
    248 	}
    249 
    250 	if (fd == -1) {
    251 		syslog(LOG_ERR, "Failed to open device %s - %s",
    252 		    devname, strerror(errno));
    253 		result->vsr_rc = VS_RESULT_ERROR;
    254 		return (result->vsr_rc);
    255 	}
    256 
    257 	/* initialize context */
    258 	(void) memset(&ctx, 0, sizeof (vs_scan_ctx_t));
    259 	ctx.vsc_idx = eng->vse_eidx;
    260 	(void) strlcpy(ctx.vsc_host, eng->vse_host, sizeof (ctx.vsc_host));
    261 	ctx.vsc_port = eng->vse_port;
    262 	ctx.vsc_sockfd = eng->vse_sockfd;
    263 	ctx.vsc_fd = fd;
    264 	ctx.vsc_fname = fname;
    265 	ctx.vsc_fsize = fsize;
    266 	ctx.vsc_flags = flags;
    267 	ctx.vsc_result = result;
    268 
    269 	/* Hooks for future saving of repaired data, not yet in use */
    270 	ctx.vsc_flags |= VS_NO_REPAIR;
    271 	ctx.vsc_repair = 0;
    272 	ctx.vsc_repair_fname = NULL;
    273 	ctx.vsc_repair_fd = -1;
    274 
    275 	/* take a copy of vs_options[idx] if they match the SE specified */
    276 	(void) pthread_mutex_lock(&vs_opt_mutex);
    277 	if (vs_icap_compare_se(ctx.vsc_idx, ctx.vsc_host, ctx.vsc_port) == 0) {
    278 		vs_icap_copy_options(&ctx.vsc_options,
    279 		    &vs_options[ctx.vsc_idx]);
    280 	}
    281 
    282 	(void) pthread_mutex_unlock(&vs_opt_mutex);
    283 
    284 	/*
    285 	 * default the result to scan engine error.
    286 	 * Any non scan-engine errors will reset it to VS_RESULT_ERROR
    287 	 */
    288 	result->vsr_rc = VS_RESULT_SE_ERROR;
    289 
    290 	/* do the scan */
    291 	if (vs_icap_option_request(&ctx) == 0)
    292 		(void) vs_icap_respmod_request(&ctx);
    293 
    294 	(void) close(fd);
    295 	vs_icap_free_options(&ctx.vsc_options);
    296 	return (result->vsr_rc);
    297 }
    298 
    299 
    300 /* ********************************************************************* */
    301 /* 			Local Function definitions			 */
    302 /* ********************************************************************* */
    303 
    304 /*
    305  * vs_icap_option_request
    306  *
    307  * Send ICAP options message and await/process the response.
    308  *
    309  * The ICAP options request needs to be sent when a connection
    310  * is first made with the scan engine. Unless the scan engine
    311  * determines that the options will never expire (which we save
    312  * as optione_req_time == -1) the request should be resent after
    313  * the expiry time specified by the icap server.
    314  *
    315  * Returns: 0 - success
    316  *         -1 - error
    317  */
    318 static int
    319 vs_icap_option_request(vs_scan_ctx_t *ctx)
    320 {
    321 	if (ctx->vsc_options.vso_req_time != -1 &&
    322 	    ((time(0) - ctx->vsc_options.vso_req_time) >
    323 	    ctx->vsc_options.vso_ttl)) {
    324 
    325 		if (vs_icap_send_option_req(ctx) < 0)
    326 			return (-1);
    327 
    328 		if (vs_icap_read_option_resp(ctx) < 0)
    329 			return (-1);
    330 
    331 		vs_icap_update_options(ctx);
    332 	}
    333 
    334 	return (0);
    335 }
    336 
    337 
    338 /*
    339  * vs_icap_send_option_req
    340  *
    341  * Send an OPTIONS request to the scan engine
    342  * The Symantec ICAP server REQUIRES the resource name (VS_SERVICE_NAME)
    343  * after the IP address, otherwise it closes the connection.
    344  *
    345  * Returns: 0 - success
    346  *         -1 - error
    347  */
    348 static int
    349 vs_icap_send_option_req(vs_scan_ctx_t *ctx)
    350 {
    351 	char my_host_name[MAXHOSTNAMELEN];
    352 	int  bufsp = VS_BUF_SZ;
    353 	char *buf0 = ctx->vsc_info.vsi_send_buf;
    354 	char *bufp = buf0;
    355 	int  tlen;
    356 
    357 	if (gethostname(my_host_name, sizeof (my_host_name)) != 0) {
    358 		/* non SE error */
    359 		ctx->vsc_result->vsr_rc = VS_RESULT_ERROR;
    360 		return (-1);
    361 	}
    362 
    363 	(void) memset(ctx->vsc_info.vsi_send_buf, 0,
    364 	    sizeof (ctx->vsc_info.vsi_send_buf));
    365 
    366 	tlen = snprintf(bufp, bufsp, "OPTIONS icap://%s:%d/%s %s\r\n",
    367 	    ctx->vsc_host, ctx->vsc_port, VS_SERVICE_NAME, VS_ICAP_VER);
    368 	bufp += tlen;
    369 	bufsp -= tlen;
    370 
    371 	tlen = snprintf(bufp, bufsp, "Host: %s\r\n\r\n", my_host_name);
    372 	bufp += tlen;
    373 
    374 	if (vs_icap_write(ctx->vsc_sockfd, buf0, (bufp - buf0)) < 0)
    375 		return (-1);
    376 
    377 	return (0);
    378 }
    379 
    380 
    381 /*
    382  * vs_icap_read_option_resp
    383  *
    384  * Returns: 0 - success
    385  *         -1 - error
    386  */
    387 static int
    388 vs_icap_read_option_resp(vs_scan_ctx_t *ctx)
    389 {
    390 	if (vs_icap_read_resp_code(ctx) < 0)
    391 		return (-1);
    392 
    393 	if (ctx->vsc_info.vsi_icap_rc != VS_RESP_OK) {
    394 		syslog(LOG_ERR, "ICAP protocol error "
    395 		    "- unexpected option response: %s",
    396 		    vs_icap_resp_str(ctx->vsc_info.vsi_icap_rc));
    397 		return (-1);
    398 	}
    399 
    400 	if (vs_icap_read_hdr(ctx, option_hdrs, VS_OPT_HDR_MAX) != 0)
    401 		return (-1);
    402 
    403 	if ((ctx->vsc_options.vso_scanstamp[0] == 0) ||
    404 	    (ctx->vsc_options.vso_respmod == 0) ||
    405 	    (ctx->vsc_options.vso_req_time == 0)) {
    406 		syslog(LOG_ERR, "ICAP protocol error "
    407 		    "- missing or invalid option response hdrs");
    408 		return (-1);
    409 	}
    410 
    411 	return (0);
    412 }
    413 
    414 
    415 /*
    416  * vs_icap_respmod_request
    417  *
    418  * Send respmod request and receive and process ICAP response.
    419  * Preview:
    420  *   ICAP allows for an optional "preview" request.  In the option negotiation,
    421  *   the server may ask for a list of types to be previewed, or to be sent
    422  *   complete (no preview).
    423  *   This is advisory. It is ok to skip the preview step, as done when the file
    424  *   is smaller than the preview_len.
    425  * Process Response:
    426  * - read and parse the RESPMOD response headers
    427  * - populate the result structure
    428  * - read any encapsulated response headers
    429  * - read any encapsulated response body and, if it represents cleaned
    430  *   file data, overwrite the file with it
    431  *
    432  * Returns: 0 - success
    433  *         -1 - error
    434  */
    435 static int
    436 vs_icap_respmod_request(vs_scan_ctx_t *ctx)
    437 {
    438 	int rv;
    439 	int bytes_sent, send_len;
    440 	uint64_t resid = ctx->vsc_fsize;
    441 
    442 	if (vs_icap_may_preview(ctx)) {
    443 
    444 		if ((rv = vs_icap_send_preview(ctx)) < 0)
    445 			return (-1);
    446 
    447 		if (vs_icap_read_respmod_resp(ctx) < 0)
    448 			return (-1);
    449 
    450 		if (ctx->vsc_info.vsi_icap_rc != VS_RESP_CONTINUE)
    451 			return (0);
    452 
    453 		bytes_sent = rv;
    454 
    455 		/* If > block (VS_BUF_SZ) remains, re-align to block boundary */
    456 		if ((ctx->vsc_fsize - (uint64_t)bytes_sent) > VS_BUF_SZ) {
    457 			send_len = VS_BUF_SZ - bytes_sent;
    458 			if ((rv = vs_icap_send_chunk(ctx, send_len)) < 0)
    459 				return (-1);
    460 			bytes_sent += rv;
    461 		}
    462 
    463 		resid -= (uint64_t)bytes_sent;
    464 
    465 	} else {
    466 
    467 		if (vs_icap_send_respmod_hdr(ctx, 0) < 0)
    468 			return (-1);
    469 	}
    470 
    471 	/* Send the remainder of the file...  */
    472 	while (resid) {
    473 		send_len = (resid > VS_BUF_SZ) ? VS_BUF_SZ : resid;
    474 
    475 		if ((rv = vs_icap_send_chunk(ctx, send_len)) < 0)
    476 			return (-1);
    477 
    478 		if (rv == 0)
    479 			break;
    480 
    481 		resid  -= (uint64_t)rv;
    482 	}
    483 
    484 	if (vs_icap_send_termination(ctx) < 0)
    485 		return (-1);
    486 
    487 	/* sending of ICAP request complete */
    488 	if (vs_icap_read_respmod_resp(ctx) < 0)
    489 		return (-1);
    490 
    491 	return (0);
    492 }
    493 
    494 
    495 /*
    496  *	vs_icap_may_preview
    497  *
    498  *	Returns: 1  - preview
    499  *	         0 - don't preview
    500  */
    501 static int
    502 vs_icap_may_preview(vs_scan_ctx_t *ctx)
    503 {
    504 	int  in_list = 0;
    505 	char *ext;
    506 	vs_options_t *opts = &ctx->vsc_options;
    507 
    508 	if (opts->vso_xfer_how == VS_PREVIEW_NONE)
    509 		return (0);
    510 
    511 	/* if the file is smaller than the preview size, don't preview */
    512 	if (ctx->vsc_fsize < (uint64_t)ctx->vsc_options.vso_preview_len)
    513 		return (0);
    514 
    515 	switch (opts->vso_xfer_how) {
    516 	case VS_PREVIEW_ALL:
    517 		return (1);
    518 	case VS_PREVIEW_EXCEPT:
    519 		/* Preview everything except types in xfer_complete */
    520 		if ((ext = vs_icap_find_ext(ctx->vsc_fname)) != 0)
    521 			in_list = vs_icap_check_ext(ext,
    522 			    opts->vso_xfer_complete);
    523 		return ((in_list) ? 0 : 1);
    524 	case VS_PREVIEW_LIST:
    525 		/* Preview only types in the the xfer_preview list  */
    526 		if ((ext = vs_icap_find_ext(ctx->vsc_fname)) != 0)
    527 			in_list = vs_icap_check_ext(ext,
    528 			    opts->vso_xfer_preview);
    529 		return ((in_list) ? 1 : 0);
    530 	}
    531 
    532 	return (1);
    533 }
    534 
    535 
    536 /*
    537  * vs_icap_find_ext
    538  *
    539  * Returns: ptr to file's extension in fname
    540  *          0 if no extension
    541  */
    542 static char *
    543 vs_icap_find_ext(char *fname)
    544 {
    545 	char *last_comp, *ext_str = 0;
    546 
    547 	if ((last_comp = strrchr(fname, '/')) != 0) {
    548 		last_comp++;
    549 	} else {
    550 		last_comp = fname;
    551 	}
    552 
    553 	/* Get file extension */
    554 	if ((ext_str = strrchr(last_comp, '.')) != 0) {
    555 		ext_str++;
    556 		if (strlen(ext_str) == 0)
    557 			ext_str = 0;
    558 	}
    559 
    560 	return (ext_str);
    561 }
    562 
    563 
    564 /*
    565  * vs_icap_send_preview
    566  *
    567  * Returns:  bytes sent (preview + alignment)
    568  *           -1 - error
    569  */
    570 static int
    571 vs_icap_send_preview(vs_scan_ctx_t *ctx)
    572 {
    573 	int preview_len = ctx->vsc_options.vso_preview_len;
    574 	int bytes_sent;
    575 
    576 	/* Send a RESPMOD request with "preview" mode.  */
    577 	if (vs_icap_send_respmod_hdr(ctx, 'P') < 0)
    578 		return (-1);
    579 
    580 	if ((bytes_sent = vs_icap_send_chunk(ctx, preview_len)) < 0)
    581 		return (-1);
    582 
    583 	if (bytes_sent < preview_len)
    584 		return (-1);
    585 
    586 	if (vs_icap_send_termination(ctx) < 0)
    587 		return (-1);
    588 
    589 	return (bytes_sent);
    590 }
    591 
    592 
    593 /*
    594  * vs_icap_send_respmod_hdr
    595  *
    596  * Create and send the RESPMOD request headers to the scan engine.
    597  *
    598  * Returns: 0 success
    599  *        < 0 error
    600  */
    601 static int
    602 vs_icap_send_respmod_hdr(vs_scan_ctx_t *ctx, int ispreview)
    603 {
    604 	int len;
    605 
    606 	if ((len = vs_icap_create_respmod_hdr(ctx, ispreview)) == -1) {
    607 		/* non SE error */
    608 		ctx->vsc_result->vsr_rc = VS_RESULT_ERROR;
    609 		return (-1);
    610 	}
    611 
    612 	/* send the headers */
    613 	if (vs_icap_write(ctx->vsc_sockfd,
    614 	    ctx->vsc_info.vsi_send_buf, len) < 0) {
    615 		return (-1);
    616 	}
    617 
    618 	return (0);
    619 }
    620 
    621 
    622 /*
    623  * vs_icap_create_respmod_hdr
    624  *
    625  * Create the RESPMOD request headers.
    626  * - RESPMOD, Host, Allow, [Preview], Encapsulated, encapsulated request hdr,
    627  *   encapsulated response hdr
    628  * Encapsulated data is sent separately subsequent to vs_icap_send_respmod_hdr,
    629  * via calls to vs_icap_send_chunk.
    630  *
    631  * The Symantec ICAP server REQUIRES the resource name (VS_SERVICE_NAME)
    632  * after the IP address, otherwise it closes the connection.
    633  *
    634  * Returns: -1 error
    635  *           length of headers data
    636  */
    637 static int
    638 vs_icap_create_respmod_hdr(vs_scan_ctx_t *ctx, int ispreview)
    639 {
    640 	char my_host_name[MAXHOSTNAMELEN];
    641 	int  hbufsp = VS_BUF_SZ;
    642 	char *hbuf0  = ctx->vsc_info.vsi_send_buf;
    643 	char *hbufp  = hbuf0;
    644 	char *encap_hdr, *encap_off0, *req_hdr, *res_hdr, *res_body;
    645 	int preview_len = ctx->vsc_options.vso_preview_len;
    646 	int  tlen;
    647 
    648 	if (gethostname(my_host_name, sizeof (my_host_name)) != 0) {
    649 		/* non SE error */
    650 		ctx->vsc_result->vsr_rc = VS_RESULT_ERROR;
    651 		return (-1);
    652 	}
    653 
    654 	(void) memset(hbufp, 0, hbufsp);
    655 
    656 	/* First the ICAP "request" part. (at offset 0) */
    657 	tlen = snprintf(hbufp, hbufsp, "RESPMOD icap://%s:%d/%s %s\r\n",
    658 	    ctx->vsc_host, ctx->vsc_port, VS_SERVICE_NAME, VS_ICAP_VER);
    659 	if (tlen >= hbufsp)
    660 		return (-1);
    661 	hbufp += tlen; hbufsp -= tlen;
    662 
    663 	tlen = snprintf(hbufp, hbufsp, "Host: %s\r\n", my_host_name);
    664 	if (tlen >= hbufsp)
    665 		return (-1);
    666 	hbufp += tlen; hbufsp -= tlen;
    667 
    668 	tlen = snprintf(hbufp, hbufsp, "Allow: 204\r\n");
    669 	if (tlen >= hbufsp)
    670 		return (-1);
    671 	hbufp += tlen; hbufsp -= tlen;
    672 
    673 	if (ispreview) {
    674 		tlen = snprintf(hbufp, hbufsp, "Preview: %d\r\n", preview_len);
    675 		if (tlen >= hbufsp)
    676 			return (-1);
    677 		hbufp += tlen; hbufsp -= tlen;
    678 	}
    679 
    680 	/* Reserve space to later insert encapsulation offsets, & blank line */
    681 	encap_hdr = hbufp;
    682 	tlen = snprintf(hbufp, hbufsp, "%*.*s\r\n\r\n",
    683 	    VS_ENCAP_SZ, VS_ENCAP_SZ, "");
    684 	if (tlen >= hbufsp)
    685 		return (-1);
    686 	hbufp += tlen; hbufsp -= tlen;
    687 
    688 	/* "offset zero" for the encapsulated parts that follow */
    689 	encap_off0 = hbufp;
    690 
    691 	/* Encapsulated request header (req_hdr) & blank line */
    692 	req_hdr = hbufp;
    693 	tlen = snprintf(hbufp, hbufsp, "GET http://%s", my_host_name);
    694 	if (tlen >= hbufsp)
    695 		return (-1);
    696 	hbufp += tlen; hbufsp -= tlen;
    697 
    698 	tlen = vs_icap_uri_encode(hbufp, hbufsp, ctx->vsc_fname);
    699 	if (tlen < 0)
    700 		return (-1);
    701 	hbufp += tlen; hbufsp -= tlen;
    702 
    703 	tlen = snprintf(hbufp, hbufsp, " HTTP/1.1\r\n\r\n");
    704 	if (tlen >= hbufsp)
    705 		return (-1);
    706 	hbufp += tlen; hbufsp -= tlen;
    707 
    708 	/* Encapsulated response header (res_hdr) & blank line */
    709 	res_hdr = hbufp;
    710 	tlen = snprintf(hbufp, hbufsp, "HTTP/1.1 200 OK\r\n");
    711 	if (tlen >= hbufsp)
    712 		return (-1);
    713 	hbufp += tlen; hbufsp -= tlen;
    714 
    715 	tlen = snprintf(hbufp, hbufsp, "Transfer-Encoding: chunked\r\n\r\n");
    716 	if (tlen >= hbufsp)
    717 		return (-1);
    718 	hbufp += tlen; hbufsp -= tlen;
    719 
    720 	/* response body section - res-body ("chunked data") */
    721 	res_body = hbufp;
    722 
    723 	/* Insert offsets in encap_hdr */
    724 	tlen = snprintf(encap_hdr, VS_ENCAP_SZ, "Encapsulated: "
    725 	    "req-hdr=%d, res-hdr=%d, res-body=%d",
    726 	    req_hdr - encap_off0, res_hdr - encap_off0, res_body - encap_off0);
    727 	/* undo the null from snprintf */
    728 	encap_hdr[tlen] = ' ';
    729 
    730 	/* return length */
    731 	return (hbufp - hbuf0);
    732 }
    733 
    734 
    735 /*
    736  * vs_icap_read_respmod_resp
    737  *
    738  * Used for both preview and final RESMOD response
    739  */
    740 static int
    741 vs_icap_read_respmod_resp(vs_scan_ctx_t *ctx)
    742 {
    743 	if (vs_icap_read_resp_code(ctx) < 0)
    744 		return (-1);
    745 
    746 	if (vs_icap_read_hdr(ctx, resp_hdrs, VS_RESP_HDR_MAX) < 0)
    747 		return (-1);
    748 
    749 	if (ctx->vsc_info.vsi_icap_rc == VS_RESP_CONTINUE) {
    750 		/* A VS_RESP_CONTINUE should not have encapsulated data */
    751 		if ((ctx->vsc_info.vsi_res_hdr) ||
    752 		    (ctx->vsc_info.vsi_res_body)) {
    753 			syslog(LOG_ERR, "ICAP protocol error -"
    754 			    "- encapsulated data in Continue response");
    755 			return (-1);
    756 		}
    757 	} else {
    758 		if (vs_icap_set_scan_result(ctx) < 0)
    759 			return (-1);
    760 
    761 		if (ctx->vsc_info.vsi_res_hdr) {
    762 			if (vs_icap_read_encap_hdr(ctx) < 0)
    763 				return (-1);
    764 		}
    765 
    766 		if (ctx->vsc_info.vsi_res_body)
    767 			vs_icap_read_encap_data(ctx);
    768 		else if (ctx->vsc_result->vsr_rc == VS_RESULT_CLEANED)
    769 			ctx->vsc_result->vsr_rc = VS_RESULT_FORBIDDEN;
    770 	}
    771 
    772 	return (0);
    773 }
    774 
    775 
    776 /*
    777  * vs_icap_read_resp_code
    778  *
    779  * Get the response code from the icap response messages
    780  */
    781 static int
    782 vs_icap_read_resp_code(vs_scan_ctx_t *ctx)
    783 {
    784 	char *buf = ctx->vsc_info.vsi_recv_buf;
    785 	int  retval;
    786 
    787 	/* Break on error or non-blank line. */
    788 	for (;;) {
    789 		(void) memset(buf, '\0', VS_BUF_SZ);
    790 
    791 		if ((retval = vs_icap_readline(ctx, buf, VS_BUF_SZ)) < 0)
    792 			return (-1);
    793 
    794 		if (retval && buf[0]) {
    795 			if (MATCH(buf, VS_ICAP_VER)) {
    796 				(void) sscanf(buf+8, "%d",
    797 				    &ctx->vsc_info.vsi_icap_rc);
    798 				return (0);
    799 			}
    800 
    801 			syslog(LOG_ERR, "ICAP protocol error -"
    802 			    "- expected ICAP/1.0, received %s", buf);
    803 
    804 			return (-1);
    805 		}
    806 	}
    807 }
    808 
    809 
    810 /*
    811  * vs_icap_read_hdr
    812  *
    813  * Reads all response headers.
    814  * As each line is read it is parsed and passed to the appropriate handler.
    815  *
    816  * Returns: 0 - success
    817  *         -1 - error
    818  */
    819 static int
    820 vs_icap_read_hdr(vs_scan_ctx_t *ctx, vs_hdr_t hdrs[], int num_hdrs)
    821 {
    822 	char *buf = ctx->vsc_info.vsi_recv_buf;
    823 	int  i, retval;
    824 	char *name, *val;
    825 
    826 	/* Break on error or blank line. */
    827 	for (;;) {
    828 		(void) memset(buf, '\0', VS_BUF_SZ);
    829 
    830 		if ((retval = vs_icap_readline(ctx, buf, VS_BUF_SZ)) < 0)
    831 			return (-1);
    832 
    833 		/* Empty line (CR/LF) normal break */
    834 		if ((retval == 0) || (!buf[0]))
    835 			break;
    836 
    837 		vs_icap_parse_hdrs(':', buf, &name, &val);
    838 
    839 		for (i = 0; i < num_hdrs; i++) {
    840 			if (strcmp(name, hdrs[i].vsh_name) == 0) {
    841 				hdrs[i].vsh_func(ctx, hdrs[i].vsh_id, val);
    842 				break;
    843 			}
    844 		}
    845 	}
    846 
    847 	return ((retval >= 0) ? 0 : -1);
    848 }
    849 
    850 
    851 /*
    852  * vs_icap_set_scan_result
    853  *
    854  * Sets the vs_result_t vsr_rc from the icap_resp_code and
    855  * any violation information in vs_result_t
    856  *
    857  * Returns: 0 - success
    858  *         -1 - error
    859  */
    860 static int
    861 vs_icap_set_scan_result(vs_scan_ctx_t *ctx)
    862 {
    863 	int i;
    864 	vs_result_t *result = ctx->vsc_result;
    865 
    866 	if (!result->vsr_scanstamp)
    867 		(void) strlcpy(result->vsr_scanstamp,
    868 		    ctx->vsc_options.vso_scanstamp, sizeof (vs_scanstamp_t));
    869 
    870 	switch (ctx->vsc_info.vsi_icap_rc) {
    871 	case VS_RESP_NO_CONT_NEEDED:
    872 		result->vsr_rc = VS_RESULT_CLEAN;
    873 		break;
    874 
    875 	case VS_RESP_OK:
    876 		/* if we have no violations , that means all ok */
    877 		if (result->vsr_nviolations == 0) {
    878 			result->vsr_rc = VS_RESULT_CLEAN;
    879 			break;
    880 		}
    881 
    882 		/* Any infections not repaired? */
    883 		result->vsr_rc = VS_RESULT_CLEANED;
    884 		for (i = 0; i < result->vsr_nviolations; i++) {
    885 			if (result->vsr_vrec[i].vr_res !=
    886 			    VS_RES_FILE_REPAIRED) {
    887 				result->vsr_rc = VS_RESULT_FORBIDDEN;
    888 				break;
    889 			}
    890 		}
    891 		break;
    892 
    893 	case VS_RESP_CREATED :
    894 		/* file is repaired */
    895 		result->vsr_rc = VS_RESULT_CLEANED;
    896 		break;
    897 
    898 	case VS_RESP_FORBIDDEN:
    899 		/* file is infected and could not be repaired */
    900 		result->vsr_rc = VS_RESULT_FORBIDDEN;
    901 		break;
    902 
    903 	default:
    904 		syslog(LOG_ERR, "ICAP protocol error "
    905 		    "- unsupported scan result: %s",
    906 		    vs_icap_resp_str(ctx->vsc_info.vsi_icap_rc));
    907 		return (-1);
    908 	}
    909 
    910 	return (0);
    911 }
    912 
    913 
    914 /*
    915  * vs_icap_read_encap_hdr
    916  *
    917  * Read the encapsulated response header to determine the length of
    918  * encapsulated data and, in some cases, to detect the infected state
    919  * of the file.
    920  *
    921  * Use of http response code:
    922  * Trend IWSS does not return virus information in the RESPMOD response
    923  * headers unless the OPTIONAL "include X_Infection_Found" checkbox is
    924  * checked and "disable_infected_url_block=yes" is set in intscan.ini.
    925  * Thus if we haven't already detected the infected/cleaned status
    926  * (ie if vsr_rc == VS_RESULT_CLEAN) we attempt to detect the
    927  * infected/cleaned state of a file from a combination of the ICAP and
    928  * http resp codes.
    929  * Here are the response code values that Trend IWSS returns:
    930  *  - clean:      icap resp = VS_RESP_NO_CONT_NEEDED
    931  *  - quarantine: icap resp = VS_RESP_OK, http resp = VS_RESP_FORBIDDEN
    932  *  - cleaned:    icap resp = VS_RESP_OK, http resp = VS_RESP_OK
    933  * For all other vendors' scan engines (so far) the infected/cleaned
    934  * state of the file has already been detected from the RESPMOD
    935  * response headers.
    936  */
    937 static int
    938 vs_icap_read_encap_hdr(vs_scan_ctx_t *ctx)
    939 {
    940 	char *buf = ctx->vsc_info.vsi_recv_buf;
    941 	char *name, *value;
    942 	int  retval;
    943 
    944 	/* Break on error or blank line. */
    945 	for (;;) {
    946 		if ((retval = vs_icap_readline(ctx, buf, VS_BUF_SZ)) < 0)
    947 			return (-1);
    948 
    949 		/* Empty line (CR/LF) normal break */
    950 		if ((retval == 0) || (!buf[0]))
    951 			break;
    952 
    953 		if (MATCH(buf, "HTTP/1.1")) {
    954 			(void) sscanf(buf + 8, "%d",
    955 			    &ctx->vsc_info.vsi_http_rc);
    956 			ctx->vsc_info.vsi_html_content = B_TRUE;
    957 
    958 			/* if not yet detected infection, interpret http_rc */
    959 			if (ctx->vsc_result->vsr_rc == VS_RESULT_CLEAN) {
    960 				if ((ctx->vsc_info.vsi_icap_rc == VS_RESP_OK) &&
    961 				    (ctx->vsc_info.vsi_http_rc == VS_RESP_OK)) {
    962 					ctx->vsc_result->vsr_rc =
    963 					    VS_RESULT_CLEANED;
    964 				} else {
    965 					ctx->vsc_result->vsr_rc =
    966 					    VS_RESULT_FORBIDDEN;
    967 				}
    968 			}
    969 		} else {
    970 			vs_icap_parse_hdrs(':', buf, &name, &value);
    971 			if (name && (MATCH(name, "Content-Length"))) {
    972 				(void) sscanf(value, "%d",
    973 				    &ctx->vsc_info.vsi_content_len);
    974 			}
    975 		}
    976 	}
    977 
    978 	return (0);
    979 }
    980 
    981 
    982 /*
    983  * vs_icap_read_encap_data
    984  *
    985  * Read the encapsulated response data.
    986  *
    987  * If the response data represents cleaned file data (for an infected file)
    988  * and VS_NO_REPAIR is not set, open repair file to save the reponse body
    989  * data in. Set the repair flag in the scan context. The repair flag is used
    990  * during the processing of the response data. If the flag is set then the
    991  * data is written to file. If any error occurs which invalidates the repaired
    992  * data file the repair flag gets reset to 0, and the data will be discarded.
    993  *
    994  * The result is reset to VS_RESULT_FORBIDDEN until all of the cleaned data
    995  * has been successfully received and processed. It is then reset to
    996  * VS_RESULT_CLEANED.
    997  *
    998  * If the data doesn't represent cleaned file data, or we cannot (or don't
    999  * want to) write the cleaned data to file, the data is discarded (repair flag
   1000  * in ctx == 0).
   1001  */
   1002 static void
   1003 vs_icap_read_encap_data(vs_scan_ctx_t *ctx)
   1004 {
   1005 	if (ctx->vsc_result->vsr_rc == VS_RESULT_CLEANED) {
   1006 		ctx->vsc_result->vsr_rc = VS_RESULT_FORBIDDEN;
   1007 
   1008 		if (!(ctx->vsc_flags & VS_NO_REPAIR)) {
   1009 			if (vs_icap_create_repair_file(ctx) == 0)
   1010 				ctx->vsc_repair = B_TRUE;
   1011 		}
   1012 	}
   1013 
   1014 	/*
   1015 	 * vs_icap_read_resp_body handles errors internally;
   1016 	 * resets ctx->vsc_repair
   1017 	 */
   1018 	(void) vs_icap_read_resp_body(ctx);
   1019 
   1020 	if (ctx->vsc_repair_fd != -1) {
   1021 		(void) close(ctx->vsc_repair_fd);
   1022 
   1023 		if (ctx->vsc_repair) {
   1024 			/* repair file contains the cleaned data */
   1025 			ctx->vsc_result->vsr_rc = VS_RESULT_CLEANED;
   1026 		} else {
   1027 			/* error occured processing data. Remove repair file */
   1028 			(void) unlink(ctx->vsc_repair_fname);
   1029 		}
   1030 	}
   1031 }
   1032 
   1033 
   1034 /*
   1035  * vs_icap_create_repair_file
   1036  *
   1037  * Create and open a file to save cleaned data in.
   1038  */
   1039 static int
   1040 vs_icap_create_repair_file(vs_scan_ctx_t *ctx)
   1041 {
   1042 	if (ctx->vsc_repair_fname == NULL)
   1043 		return (-1);
   1044 
   1045 	if ((ctx->vsc_repair_fd = open(ctx->vsc_repair_fname,
   1046 	    O_RDWR | O_CREAT | O_EXCL | O_TRUNC, 0644)) == -1) {
   1047 		return (-1);
   1048 	}
   1049 
   1050 	return (0);
   1051 }
   1052 
   1053 
   1054 /*
   1055  * vs_icap_read_resp_body
   1056  *
   1057  * Repeatedly call vs_icap_read_body_chunk until it returns:
   1058  *    0 indicating that there's no more data to read or
   1059  *   -1 indicating a read error -> reset ctx->vsc_repair 0
   1060  *
   1061  * Returns: 0 success
   1062  *         -1 error
   1063  */
   1064 static int
   1065 vs_icap_read_resp_body(vs_scan_ctx_t *ctx)
   1066 {
   1067 	int retval;
   1068 
   1069 	while ((retval = vs_icap_read_body_chunk(ctx)) > 0)
   1070 		;
   1071 
   1072 	if (retval < 0)
   1073 		ctx->vsc_repair = B_FALSE;
   1074 
   1075 	return (retval);
   1076 }
   1077 
   1078 
   1079 /*
   1080  * vs_icap_read_body_chunk
   1081  *
   1082  * Read the chunk size, then read the chunk of data and write the
   1083  * data to file repair_fd (or discard it).
   1084  * If the data cannot be successfully written to file, set repair
   1085  * flag in ctx to 0, and discard all subsequent data.
   1086  *
   1087  * Returns: chunk size
   1088  *          -1 on error
   1089  */
   1090 static int
   1091 vs_icap_read_body_chunk(vs_scan_ctx_t *ctx)
   1092 {
   1093 	char *lbuf = ctx->vsc_info.vsi_recv_buf;
   1094 	unsigned int chunk_size, resid;
   1095 	int rsize;
   1096 
   1097 	/* Read and parse the chunk size. */
   1098 	if ((vs_icap_readline(ctx, lbuf, VS_BUF_SZ) < 0) ||
   1099 	    (!sscanf(lbuf, "%x", &chunk_size))) {
   1100 		return (-1);
   1101 	}
   1102 
   1103 	/* Read and save/discard chunk */
   1104 	resid = chunk_size;
   1105 	while (resid) {
   1106 		rsize = (resid < VS_BUF_SZ) ? resid : VS_BUF_SZ;
   1107 
   1108 		if ((rsize = vs_icap_read(ctx->vsc_sockfd, lbuf, rsize)) <= 0)
   1109 			return (-1);
   1110 
   1111 		if (ctx->vsc_repair) {
   1112 			if (vs_icap_write(ctx->vsc_repair_fd, lbuf, rsize) < 0)
   1113 				ctx->vsc_repair = B_FALSE;
   1114 		}
   1115 
   1116 		resid -= rsize;
   1117 	}
   1118 
   1119 	/* Eat one CR/LF after the data */
   1120 	if (vs_icap_readline(ctx, lbuf, VS_BUF_SZ) < 0)
   1121 		return (-1);
   1122 
   1123 	if (lbuf[0]) {
   1124 		syslog(LOG_ERR, "ICAP protocol error - expected blank line");
   1125 		return (-1);
   1126 	}
   1127 
   1128 	return (chunk_size);
   1129 }
   1130 
   1131 
   1132 /* *********************************************************************** */
   1133 /* 			Utility read, write functions			   */
   1134 /* *********************************************************************** */
   1135 
   1136 /*
   1137  * vs_icap_write
   1138  *
   1139  * Return: 0 if all data successfully written
   1140  *        -1 otherwise
   1141  */
   1142 static int
   1143 vs_icap_write(int fd, char *buf, int buflen)
   1144 {
   1145 	char *ptr = buf;
   1146 	int resid = buflen;
   1147 	int bytes_sent = 0;
   1148 
   1149 	while (resid > 0) {
   1150 		errno = 0;
   1151 		bytes_sent = write(fd, ptr, resid);
   1152 		if (bytes_sent < 0) {
   1153 			if (errno == EINTR)
   1154 				continue;
   1155 			else
   1156 				return (-1);
   1157 		}
   1158 		resid -= bytes_sent;
   1159 		ptr += bytes_sent;
   1160 	}
   1161 
   1162 	return (0);
   1163 }
   1164 
   1165 
   1166 /*
   1167  * vs_icap_read
   1168  *
   1169  * Returns: bytes_read (== len unless EOF hit before len bytes read)
   1170  *          -1 error
   1171  */
   1172 static int
   1173 vs_icap_read(int fd, char *buf, int len)
   1174 {
   1175 	char *ptr = buf;
   1176 	int resid = len;
   1177 	int bytes_read = 0;
   1178 
   1179 	while (resid > 0) {
   1180 		errno = 0;
   1181 		bytes_read = read(fd, ptr, resid);
   1182 		if (bytes_read < 0) {
   1183 			if (errno == EINTR)
   1184 				continue;
   1185 			else
   1186 				return (-1);
   1187 		}
   1188 		resid -= bytes_read;
   1189 		ptr += bytes_read;
   1190 	}
   1191 
   1192 	return (len - resid);
   1193 }
   1194 
   1195 
   1196 /*
   1197  * vs_icap_send_chunk
   1198  *
   1199  * Send a "chunk" of file data, containing:
   1200  * - Length (in hex) CR/NL
   1201  * - [optiona data]
   1202  * - CR/NL
   1203  *
   1204  * Returns: data length sent (not including encapsulation)
   1205  *          -1 - error
   1206  */
   1207 static int
   1208 vs_icap_send_chunk(vs_scan_ctx_t *ctx, int chunk_len)
   1209 {
   1210 	char *hdr = ctx->vsc_info.vsi_send_hdr;
   1211 	char *dbuf = ctx->vsc_info.vsi_send_buf;
   1212 	char *tail;
   1213 	char head[VS_HDR_SZ + 1];
   1214 	int nread = 0, hlen, tlen = 2;
   1215 
   1216 	if (chunk_len > VS_BUF_SZ)
   1217 		chunk_len = VS_BUF_SZ;
   1218 
   1219 	/* Read the data. */
   1220 	if ((nread = vs_icap_read(ctx->vsc_fd, dbuf, chunk_len)) < 0)
   1221 		return (-1);
   1222 
   1223 	if (nread > 0) {
   1224 		/* wrap data in a header and trailer */
   1225 		hlen = snprintf(head, sizeof (head), "%x\r\n", nread);
   1226 		hdr += (VS_HDR_SZ - hlen);
   1227 		(void) memcpy(hdr, head, hlen);
   1228 		tail = dbuf + nread;
   1229 		tail[0] = '\r';
   1230 		tail[1] = '\n';
   1231 
   1232 		if (vs_icap_write(ctx->vsc_sockfd, hdr,
   1233 		    hlen + nread + tlen) < 0) {
   1234 			return (-1);
   1235 		}
   1236 	}
   1237 
   1238 	return (nread);
   1239 }
   1240 
   1241 
   1242 /*
   1243  * vs_icap_send_termination
   1244  *
   1245  * Send 0 length termination to scan engine: "0\r\n\r\n"
   1246  *
   1247  * Returns: 0 - success
   1248  *         -1 - error
   1249  */
   1250 static int
   1251 vs_icap_send_termination(vs_scan_ctx_t *ctx)
   1252 {
   1253 	if (vs_icap_write(ctx->vsc_sockfd, VS_TERMINATION,
   1254 	    strlen(VS_TERMINATION)) < 0) {
   1255 		return (-1);
   1256 	}
   1257 
   1258 	return (0);
   1259 }
   1260 
   1261 
   1262 /*
   1263  * vs_icap_readline
   1264  *
   1265  * Read a line of response data from the socket. \n indicates end of line.
   1266  *
   1267  *  Returns: bytes read
   1268  *          -1 - error
   1269  */
   1270 static int
   1271 vs_icap_readline(vs_scan_ctx_t *ctx, char *buf, int buflen)
   1272 {
   1273 	char c;
   1274 	int i, retval;
   1275 
   1276 	i = 0;
   1277 	for (;;) {
   1278 		errno = 0;
   1279 		retval = recv(ctx->vsc_sockfd, &c, 1, 0);
   1280 
   1281 		if (retval < 0 && errno == EINTR)
   1282 			continue;
   1283 
   1284 		if (retval <= 0) {
   1285 			if (vscand_get_state() != VS_STATE_SHUTDOWN) {
   1286 				syslog(LOG_ERR, "Error receiving data from "
   1287 				    "Scan Engine: %s", strerror(errno));
   1288 			}
   1289 			return (-1);
   1290 		}
   1291 
   1292 		buf[i++] = c;
   1293 		if (c == '\n')
   1294 			break;
   1295 
   1296 		if (i >= (buflen - 2))
   1297 			return (-1);
   1298 	}
   1299 
   1300 	buf[i] = '\0';
   1301 
   1302 	/* remove preceding and trailing whitespace */
   1303 	vs_icap_trimspace(buf);
   1304 
   1305 	return (i);
   1306 }
   1307 
   1308 
   1309 /* ************************************************************************ */
   1310 /* 				HEADER processing			    */
   1311 /* ************************************************************************ */
   1312 
   1313 /*
   1314  * vs_icap_parse_hdrs
   1315  *
   1316  * parse an icap hdr line to find name and value
   1317  */
   1318 static void
   1319 vs_icap_parse_hdrs(char delimiter, char *line, char **name, char **val)
   1320 {
   1321 	char *q = line;
   1322 	int line_len;
   1323 
   1324 	/* strip any spaces */
   1325 	while (*q == ' ')
   1326 		q++;
   1327 
   1328 	*name = q;
   1329 	*val = 0;
   1330 
   1331 	/* Empty line is normal termination */
   1332 	if ((line_len = strlen(line)) == 0)
   1333 		return;
   1334 
   1335 	if ((q = strchr(line, delimiter)) != 0) {
   1336 		*q++ = '\0';
   1337 	} else {
   1338 		q = line + line_len;
   1339 	}
   1340 
   1341 	/* value part follows spaces */
   1342 	while (*q == ' ')
   1343 		q++;
   1344 
   1345 	*val = q;
   1346 }
   1347 
   1348 
   1349 /*
   1350  * vs_icap_resp_violations
   1351  */
   1352 /*ARGSUSED*/
   1353 static int
   1354 vs_icap_resp_violations(vs_scan_ctx_t *ctx, int hdr_id, char *line)
   1355 {
   1356 	int i, rv, vcnt;
   1357 
   1358 	(void) sscanf(line, "%d", &vcnt);
   1359 
   1360 	ctx->vsc_result->vsr_nviolations =
   1361 	    (vcnt > VS_MAX_VIOLATIONS) ? VS_MAX_VIOLATIONS : vcnt;
   1362 
   1363 	ctx->vsc_info.vsi_threat_hdr = VS_RESP_X_VIOLATIONS;
   1364 
   1365 	for (i = 0; i < vcnt; i++) {
   1366 		if ((rv = vs_icap_resp_violation_rec(ctx, i)) < 0)
   1367 			return (rv);
   1368 
   1369 	}
   1370 
   1371 	return (1);
   1372 }
   1373 
   1374 
   1375 /*
   1376  * vs_icap_resp_violation_rec
   1377  *
   1378  * take all violation data (up to VS_MAX_VIOLATIONS) and save it
   1379  * in violation_info.
   1380  * each violation has 4 lines of info: doc name, virus name,
   1381  * virus id and resolution
   1382  */
   1383 static int
   1384 vs_icap_resp_violation_rec(vs_scan_ctx_t *ctx, int vr_idx)
   1385 {
   1386 	int vline;
   1387 	int retval = 0;
   1388 	char *buf = ctx->vsc_info.vsi_recv_buf;
   1389 	vs_vrec_t *vr;
   1390 
   1391 	if (vr_idx < VS_MAX_VIOLATIONS) {
   1392 		vr = &ctx->vsc_result->vsr_vrec[vr_idx];
   1393 	} else {
   1394 		vr = 0;
   1395 	}
   1396 
   1397 	for (vline = 0; vline < VS_VIOLATION_LINES; vline++) {
   1398 		if ((retval = vs_icap_readline(ctx, buf, VS_BUF_SZ)) < 0)
   1399 			return (-1);
   1400 
   1401 		/* empty line? */
   1402 		if ((retval == 0) || (!buf[0]))
   1403 			break;
   1404 
   1405 		if (vr) {
   1406 			switch (vline) {
   1407 			case 0: /* doc name */
   1408 				break;
   1409 			case 1: /* Threat Description */
   1410 				(void) strlcpy(vr->vr_desc, buf,
   1411 				    VS_DESCRIPTION_MAX);
   1412 				break;
   1413 			case 2: /* Problem ID */
   1414 				(void) sscanf(buf, "%d", &vr->vr_id);
   1415 				break;
   1416 			case 3: /* Resolution */
   1417 				(void) sscanf(buf, "%d", &vr->vr_res);
   1418 				break;
   1419 			}
   1420 		}
   1421 	}
   1422 
   1423 	return (1);
   1424 }
   1425 
   1426 
   1427 /*
   1428  * vs_icap_opt_value
   1429  * given an icap options hdr string, process value
   1430  */
   1431 static int
   1432 vs_icap_opt_value(vs_scan_ctx_t *ctx, int hdr_id, char *line)
   1433 {
   1434 	int x;
   1435 	long val;
   1436 	char *end;
   1437 
   1438 	switch (hdr_id) {
   1439 	case VS_OPT_PREVIEW:
   1440 		(void) sscanf(line, "%d", &x);
   1441 		if (x < VS_MIN_PREVIEW_LEN)
   1442 			x = VS_MIN_PREVIEW_LEN;
   1443 		if (x > VS_BUF_SZ)
   1444 			x = VS_BUF_SZ;
   1445 		ctx->vsc_options.vso_preview_len = x;
   1446 		break;
   1447 
   1448 	case VS_OPT_TTL:
   1449 		if (*line == 0) {
   1450 			ctx->vsc_options.vso_req_time = -1;
   1451 			break;
   1452 		}
   1453 
   1454 		val = strtol(line, &end, 10);
   1455 		if ((end != (line + strlen(line))) || (val < 0))
   1456 			break;
   1457 
   1458 		ctx->vsc_options.vso_ttl = val;
   1459 		ctx->vsc_options.vso_req_time = time(0);
   1460 		break;
   1461 
   1462 	case VS_OPT_ALLOW:
   1463 		(void) sscanf(line, "%d", &ctx->vsc_options.vso_allow);
   1464 		break;
   1465 
   1466 	case VS_OPT_SERVICE:
   1467 		(void) strlcpy(ctx->vsc_options.vso_service, line,
   1468 		    VS_SERVICE_SZ);
   1469 		break;
   1470 
   1471 	case VS_OPT_X_DEF_INFO:
   1472 		(void) strlcpy(ctx->vsc_options.vso_defninfo, line,
   1473 		    VS_DEFN_SZ);
   1474 		break;
   1475 
   1476 	case VS_OPT_METHODS:
   1477 		if (strstr(line, "RESPMOD") != NULL)
   1478 			ctx->vsc_options.vso_respmod = 1;
   1479 		break;
   1480 
   1481 	case VS_OPT_ISTAG:
   1482 		vs_icap_istag_to_scanstamp(line,
   1483 		    ctx->vsc_options.vso_scanstamp);
   1484 		break;
   1485 
   1486 	default:
   1487 		break;
   1488 
   1489 	}
   1490 
   1491 	return (1);
   1492 }
   1493 
   1494 
   1495 /*
   1496  * vs_icap_resp_istag
   1497  *
   1498  * Called to handle ISTAG when received in RESPMOD response.
   1499  *  - populate result->vsr_scanstamp from istag
   1500  *  - update the scanstamp in vs_options and log the update.
   1501  */
   1502 /*ARGSUSED*/
   1503 static int
   1504 vs_icap_resp_istag(vs_scan_ctx_t *ctx, int hdr_id, char *line)
   1505 {
   1506 	vs_icap_istag_to_scanstamp(line, ctx->vsc_result->vsr_scanstamp);
   1507 
   1508 	/* update the scanstamp in vs_options */
   1509 	(void) pthread_mutex_lock(&vs_opt_mutex);
   1510 	if (vs_icap_compare_se(ctx->vsc_idx,
   1511 	    ctx->vsc_host, ctx->vsc_port) == 0) {
   1512 		if (strcmp(vs_options[ctx->vsc_idx].vso_scanstamp,
   1513 		    ctx->vsc_result->vsr_scanstamp) != 0) {
   1514 			(void) strlcpy(vs_options[ctx->vsc_idx].vso_scanstamp,
   1515 			    ctx->vsc_result->vsr_scanstamp,
   1516 			    sizeof (vs_scanstamp_t));
   1517 		}
   1518 	}
   1519 	(void) pthread_mutex_unlock(&vs_opt_mutex);
   1520 
   1521 	return (1);
   1522 }
   1523 
   1524 
   1525 /*
   1526  * vs_icap_istag_to_scanstamp
   1527  *
   1528  * Copies istag into scanstamp, stripping leading and trailing
   1529  * quotes '"' from istag. If the istag is invalid (too long)
   1530  * scanstamp will be left unchanged.
   1531  *
   1532  * vs_scanstamp_t is defined to be large enough to hold the
   1533  * istag plus a null terminator.
   1534  */
   1535 static void
   1536 vs_icap_istag_to_scanstamp(char *istag, vs_scanstamp_t scanstamp)
   1537 {
   1538 	char *p = istag;
   1539 	int len;
   1540 
   1541 	/* eliminate preceding '"' */
   1542 	if (p[0] == '"')
   1543 		++p;
   1544 
   1545 	/* eliminate trailing '"' */
   1546 	len = strlen(p);
   1547 	if (p[len - 1] == '"')
   1548 		--len;
   1549 
   1550 	if (len < sizeof (vs_scanstamp_t))
   1551 		(void) strlcpy(scanstamp, p, len + 1);
   1552 }
   1553 
   1554 
   1555 /*
   1556  * vs_icap_opt_ext
   1557  *
   1558  * read the transfer preview / transfer complete headers to
   1559  * determine which file types can be previewed
   1560  */
   1561 static int
   1562 vs_icap_opt_ext(vs_scan_ctx_t *ctx, int hdr_id, char *line)
   1563 {
   1564 	vs_options_t *opt = &ctx->vsc_options;
   1565 
   1566 	switch (hdr_id) {
   1567 	case VS_OPT_XFER_PREVIEW:
   1568 		if (opt->vso_xfer_preview) {
   1569 			free(opt->vso_xfer_preview);
   1570 			opt->vso_xfer_preview = 0;
   1571 		}
   1572 		if (strstr(line, "*")) {
   1573 			opt->vso_xfer_how = VS_PREVIEW_ALL;
   1574 		} else {
   1575 			opt->vso_xfer_preview = vs_icap_make_strvec
   1576 			    (line, EXT_SEPARATOR);
   1577 			opt->vso_xfer_how = VS_PREVIEW_LIST;
   1578 		}
   1579 		break;
   1580 
   1581 	case VS_OPT_XFER_COMPLETE :
   1582 		if (opt->vso_xfer_complete) {
   1583 			free(opt->vso_xfer_complete);
   1584 			opt->vso_xfer_complete = 0;
   1585 		}
   1586 		if (strstr(line, "*")) {
   1587 			opt->vso_xfer_how = VS_PREVIEW_NONE;
   1588 		} else {
   1589 			opt->vso_xfer_complete = vs_icap_make_strvec
   1590 			    (line, EXT_SEPARATOR);
   1591 			opt->vso_xfer_how = VS_PREVIEW_EXCEPT;
   1592 		}
   1593 		break;
   1594 	default:
   1595 		break;
   1596 	}
   1597 
   1598 	return (1);
   1599 }
   1600 
   1601 
   1602 /*
   1603  * vs_icap_resp_infection
   1604  *
   1605  * read the type, resolution and threat description for each
   1606  * reported violation and save in ctx->vsc_result
   1607  */
   1608 /*ARGSUSED*/
   1609 static int
   1610 vs_icap_resp_infection(vs_scan_ctx_t *ctx, int hdr_id, char *line)
   1611 {
   1612 	char *name, *val;
   1613 	int i, got = 0;
   1614 	int type = 0, res = 0;
   1615 	char *desc = 0;
   1616 	vs_vrec_t *vr = 0;
   1617 
   1618 	for (i = 0; i < VS_INFECTION_FIELDS; i++) {
   1619 		vs_icap_parse_hdrs('=', line, &name, &val);
   1620 
   1621 		switch (i) {
   1622 		case 0:
   1623 			if (MATCH(name, "Type")) {
   1624 				(void) sscanf(val, "%d", &type);
   1625 				got++;
   1626 			}
   1627 			break;
   1628 		case 1:
   1629 			if (MATCH(name, "Resolution")) {
   1630 				(void) sscanf(val, "%d", &res);
   1631 				got++;
   1632 			}
   1633 			break;
   1634 		case 2:
   1635 			if (MATCH(name, "Threat")) {
   1636 				desc = val;
   1637 				got++;
   1638 			}
   1639 			break;
   1640 		default :
   1641 			break;
   1642 		}
   1643 
   1644 		if ((line = strstr(val, ";")))
   1645 			line++;
   1646 	}
   1647 
   1648 	if (got != VS_INFECTION_FIELDS)
   1649 		return (0);
   1650 
   1651 	/*
   1652 	 * We may have info from an X-Violations-Found record, (which provides
   1653 	 * more complete information). If so, don't destroy what we have.
   1654 	 */
   1655 	if ((ctx->vsc_result->vsr_nviolations == 0) ||
   1656 	    (ctx->vsc_info.vsi_threat_hdr < VS_RESP_X_INFECTION)) {
   1657 		vr = &ctx->vsc_result->vsr_vrec[0];
   1658 		vr->vr_id = type;
   1659 		vr->vr_res = res;
   1660 		(void) strlcpy(vr->vr_desc, desc, VS_DESCRIPTION_MAX);
   1661 		ctx->vsc_result->vsr_nviolations = 1;
   1662 
   1663 		ctx->vsc_info.vsi_threat_hdr = VS_RESP_X_INFECTION;
   1664 	}
   1665 
   1666 	return (1);
   1667 }
   1668 
   1669 
   1670 /*
   1671  * vs_icap_resp_virus_id
   1672  *
   1673  * X-Virus-ID is defined as being a shorter alternative to X-Infection-Found.
   1674  * If we already have virus information, from either X-Infection-Found or
   1675  * X-Violations-Found, it will be more complete, so don't overwrite it with
   1676  * the info from X-Virus-ID.
   1677  */
   1678 /*ARGSUSED*/
   1679 static int
   1680 vs_icap_resp_virus_id(vs_scan_ctx_t *ctx, int hdr_id, char *line)
   1681 {
   1682 	vs_vrec_t *vr = 0;
   1683 
   1684 	if (ctx->vsc_result->vsr_nviolations == 0) {
   1685 		vr = &ctx->vsc_result->vsr_vrec[0];
   1686 		vr->vr_id = 0;
   1687 		vr->vr_res = 0;
   1688 		(void) strlcpy(vr->vr_desc, line, VS_DESCRIPTION_MAX);
   1689 		ctx->vsc_result->vsr_nviolations = 1;
   1690 
   1691 		ctx->vsc_info.vsi_threat_hdr = VS_RESP_X_VIRUS_ID;
   1692 	}
   1693 
   1694 	return (1);
   1695 }
   1696 
   1697 
   1698 /*
   1699  * vs_icap_resp_encap
   1700  *
   1701  * get the encapsulated header info
   1702  */
   1703 /*ARGSUSED*/
   1704 static int
   1705 vs_icap_resp_encap(vs_scan_ctx_t *ctx, int hdr_id, char *line)
   1706 {
   1707 	if (strstr(line, "res-hdr"))
   1708 		ctx->vsc_info.vsi_res_hdr = B_TRUE;
   1709 
   1710 	if (strstr(line, "res-body"))
   1711 		ctx->vsc_info.vsi_res_body = B_TRUE;
   1712 
   1713 	return (1);
   1714 }
   1715 
   1716 
   1717 /*
   1718  * Utility functions for handling OPTIONS data: vs_options_t
   1719  */
   1720 
   1721 /*
   1722  * vs_icap_compare_scanstamp
   1723  * compare scanstamp with that stored for engine idx
   1724  *
   1725  * Returns: 0 - if equal
   1726  */
   1727 int
   1728 vs_icap_compare_scanstamp(int idx, vs_scanstamp_t scanstamp)
   1729 {
   1730 	int rc;
   1731 
   1732 	if (!scanstamp || scanstamp[0] == '\0')
   1733 		return (-1);
   1734 
   1735 	(void) pthread_mutex_lock(&vs_opt_mutex);
   1736 	rc = strcmp(scanstamp, vs_options[idx].vso_scanstamp);
   1737 	(void) pthread_mutex_unlock(&vs_opt_mutex);
   1738 
   1739 	return (rc);
   1740 }
   1741 
   1742 
   1743 /*
   1744  * vs_icap_compare_se
   1745  * compare host and port with that stored for engine idx
   1746  *
   1747  * Returns: 0 - if equal
   1748  */
   1749 static int
   1750 vs_icap_compare_se(int idx, char *host, int port)
   1751 {
   1752 	if (vs_options[idx].vso_port != port)
   1753 		return (-1);
   1754 
   1755 	if (strcmp(vs_options[idx].vso_host, host) != 0)
   1756 		return (-1);
   1757 
   1758 	return (0);
   1759 }
   1760 
   1761 
   1762 /*
   1763  * vs_icap_free_options
   1764  *
   1765  * Free dynamic parts of vs_options_t: xfer_preview, xfer_complete
   1766  */
   1767 static void
   1768 vs_icap_free_options(vs_options_t *options)
   1769 {
   1770 	if (options->vso_xfer_preview)
   1771 		free(options->vso_xfer_preview);
   1772 
   1773 	if (options->vso_xfer_complete)
   1774 		free(options->vso_xfer_complete);
   1775 
   1776 	(void) memset(options, 0, sizeof (vs_options_t));
   1777 }
   1778 
   1779 
   1780 /*
   1781  * vs_icap_copy_options
   1782  */
   1783 void
   1784 vs_icap_copy_options(vs_options_t *to_opt, vs_options_t *from_opt)
   1785 {
   1786 	*to_opt = *from_opt;
   1787 
   1788 	if (from_opt->vso_xfer_preview) {
   1789 		to_opt->vso_xfer_preview =
   1790 		    vs_icap_copy_strvec(from_opt->vso_xfer_preview);
   1791 	}
   1792 
   1793 	if (from_opt->vso_xfer_complete) {
   1794 		to_opt->vso_xfer_complete =
   1795 		    vs_icap_copy_strvec(from_opt->vso_xfer_complete);
   1796 	}
   1797 }
   1798 
   1799 
   1800 /*
   1801  * vs_icap_update_options
   1802  */
   1803 static void
   1804 vs_icap_update_options(vs_scan_ctx_t *ctx)
   1805 {
   1806 	int idx = ctx->vsc_idx;
   1807 
   1808 	(void) pthread_mutex_lock(&vs_opt_mutex);
   1809 
   1810 	if (vs_icap_compare_se(idx, ctx->vsc_host, ctx->vsc_port) == 0) {
   1811 		vs_icap_free_options(&vs_options[idx]);
   1812 		vs_icap_copy_options(&vs_options[idx], &ctx->vsc_options);
   1813 	}
   1814 
   1815 	(void) pthread_mutex_unlock(&vs_opt_mutex);
   1816 }
   1817 
   1818 
   1819 /*
   1820  * vs_icap_make_strvec
   1821  *
   1822  * Populate a iovec_t from line, where line is a string of 'sep'
   1823  * separated fields. Within the copy of line in the iovec_t each
   1824  * field will be null terminated with leading & trailing whitespace
   1825  * removed. This allows for fast searching.
   1826  *
   1827  * The iovec_t itself and the data it points to are allocated
   1828  * as a single chunk.
   1829  */
   1830 static iovec_t *
   1831 vs_icap_make_strvec(char *line, const char *sep)
   1832 {
   1833 	iovec_t *vec;
   1834 	char *tmp, *ctx;
   1835 	int datalen, len;
   1836 
   1837 	datalen = strlen(line) + 1;
   1838 	len = sizeof (iovec_t) + datalen;
   1839 
   1840 	if ((vec = (iovec_t *)calloc(1, len)) == 0)
   1841 		return (0);
   1842 
   1843 	vec->iov_len = len;
   1844 	vec->iov_base = (char *)vec + sizeof (iovec_t);
   1845 	(void) strlcpy(vec->iov_base, line, datalen);
   1846 
   1847 	/* tokenize data for easier searching */
   1848 	for (tmp = strtok_r(vec->iov_base, sep, &ctx); tmp;
   1849 	    tmp = strtok_r(0, sep, &ctx)) {
   1850 	}
   1851 
   1852 	return (vec);
   1853 }
   1854 
   1855 
   1856 /*
   1857  * vs_icap_copy_strvec
   1858  *
   1859  * allocate and copy strvec
   1860  */
   1861 static iovec_t *
   1862 vs_icap_copy_strvec(iovec_t *from_vec)
   1863 {
   1864 	iovec_t *to_vec;
   1865 
   1866 	if ((to_vec = (iovec_t *)calloc(1, from_vec->iov_len)) == 0)
   1867 		return (0);
   1868 
   1869 	bcopy(from_vec, to_vec, from_vec->iov_len);
   1870 	to_vec->iov_base = (char *)to_vec + sizeof (iovec_t);
   1871 
   1872 	return (to_vec);
   1873 }
   1874 
   1875 
   1876 /*
   1877  * vs_icap_check_ext
   1878  *
   1879  * Returns: 1 - if ext in strvec
   1880  *          0 - otherwise
   1881  */
   1882 static int
   1883 vs_icap_check_ext(char *ext, iovec_t *vec)
   1884 {
   1885 	char *p, *end = (char *)vec + vec->iov_len;
   1886 
   1887 	for (p = vec->iov_base;  p < end; p += strlen(p) + 1) {
   1888 		if (MATCH(ext, p))
   1889 			return (1);
   1890 	}
   1891 
   1892 	return (0);
   1893 }
   1894 
   1895 
   1896 /*
   1897  * vs_icap_resp_str
   1898  */
   1899 static char *
   1900 vs_icap_resp_str(int rc)
   1901 {
   1902 	vs_resp_msg_t *p = icap_resp;
   1903 
   1904 	if (rc < 0)
   1905 		rc = -rc;
   1906 
   1907 	while (p->vsm_rc != VS_RESP_UNKNOWN) {
   1908 		if (p->vsm_rc == rc)
   1909 			break;
   1910 		p++;
   1911 	}
   1912 
   1913 	return (p->vsm_msg);
   1914 }
   1915 
   1916 
   1917 /*
   1918  * vs_icap_trimspace
   1919  *
   1920  * Trims whitespace from both the beginning and end of a string. This
   1921  * function alters the string buffer in-place.
   1922  *
   1923  * Whitespaces found at the beginning of the string are eliminated by
   1924  * moving forward the start of the string at the first non-whitespace
   1925  * character.
   1926  * Whitespace found at the end of the string are overwritten with nulls.
   1927  *
   1928  */
   1929 static void
   1930 vs_icap_trimspace(char *buf)
   1931 {
   1932 	char *p = buf;
   1933 	char *q = buf;
   1934 
   1935 	if (buf == 0)
   1936 		return;
   1937 
   1938 	while (*p && isspace(*p))
   1939 		++p;
   1940 
   1941 	while ((*q = *p++) != 0)
   1942 	++q;
   1943 
   1944 	if (q != buf) {
   1945 		while ((--q, isspace(*q)) != 0)
   1946 			*q = '\0';
   1947 	}
   1948 }
   1949 
   1950 
   1951 /*
   1952  * vs_icap_uri_encode
   1953  *
   1954  * Encode uri data (eg filename) in accordance with RFC 2396
   1955  * 'Illegal' characters should be replaced with %hh, where hh is
   1956  * the hex value of the character. For example a space would be
   1957  * replaced with %20.
   1958  * Filenames are all already UTF-8 encoded. Any UTF-8 octects that
   1959  * are 'illegal' characters will be encoded as described above.
   1960  *
   1961  * Paramaters: data - string to be encoded (NULL terminated)
   1962  *             buf  - output buffer (NULL terminated)
   1963  *             size - size of output buffer
   1964  *
   1965  * Returns: strlen of encoded data on success
   1966  *			-1 size on error (contents of buf undefined)
   1967  */
   1968 static int
   1969 vs_icap_uri_encode(char *buf, int size, char *data)
   1970 {
   1971 	unsigned char *iptr;
   1972 	char *optr = buf;
   1973 	int len = strlen(data);
   1974 
   1975 	/* modify the data */
   1976 	for (iptr = (unsigned char *)data; *iptr; iptr++) {
   1977 		if (vs_icap_uri_illegal_char(*iptr)) {
   1978 			if ((len += 2) >= size)
   1979 				return (-1);
   1980 			(void) sprintf(optr, "%%%0x", *iptr);
   1981 			optr += 3;
   1982 		} else {
   1983 			if (len >= size)
   1984 				return (-1);
   1985 			*optr++ = *iptr;
   1986 		}
   1987 	}
   1988 
   1989 	*optr = '\0';
   1990 	return (len);
   1991 }
   1992 
   1993 
   1994 /*
   1995  * vs_icap_uri_illegal_char
   1996  *
   1997  * The following us-ascii characters (UTF-8 octets) are 'illegal':
   1998  * < > # % " { } | \ ^ [ ] ` space, 0x01 -> 0x1F & 0x7F
   1999  * All non us-ascii UTF-8 octets ( >= 0x80) are illegal.
   2000  *
   2001  * Returns: 1 if character is not allowed in a URI
   2002  *          0 otherwise
   2003  */
   2004 static int
   2005 vs_icap_uri_illegal_char(char c)
   2006 {
   2007 	static const char *uri_illegal_chars = "<>#%\" {}|\\^[]`";
   2008 
   2009 	/* us-ascii non printable characters or non us-ascii */
   2010 	if ((c <= 0x1F) || (c >= 0x7F))
   2011 		return (1);
   2012 
   2013 	/* us-ascii dis-allowed characters */
   2014 	if (strchr(uri_illegal_chars, c))
   2015 		return (1);
   2016 
   2017 	return (0);
   2018 
   2019 }
   2020