xref: /titanic_50/usr/src/cmd/vscan/vscand/vs_icap.c (revision 9aa73b6813b3fd35e78fcc44fd17535e765e504c)
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