xref: /illumos-gate/usr/src/cmd/vscan/vscand/vs_icap.c (revision 8119dad84d6416f13557b0ba8e2aaf9064cbcfd3)
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 	(void) strlcpy(result->vsr_scanstamp,
867 	    ctx->vsc_options.vso_scanstamp, sizeof (vs_scanstamp_t));
868 
869 	switch (ctx->vsc_info.vsi_icap_rc) {
870 	case VS_RESP_NO_CONT_NEEDED:
871 		result->vsr_rc = VS_RESULT_CLEAN;
872 		break;
873 
874 	case VS_RESP_OK:
875 		/* if we have no violations , that means all ok */
876 		if (result->vsr_nviolations == 0) {
877 			result->vsr_rc = VS_RESULT_CLEAN;
878 			break;
879 		}
880 
881 		/* Any infections not repaired? */
882 		result->vsr_rc = VS_RESULT_CLEANED;
883 		for (i = 0; i < result->vsr_nviolations; i++) {
884 			if (result->vsr_vrec[i].vr_res !=
885 			    VS_RES_FILE_REPAIRED) {
886 				result->vsr_rc = VS_RESULT_FORBIDDEN;
887 				break;
888 			}
889 		}
890 		break;
891 
892 	case VS_RESP_CREATED :
893 		/* file is repaired */
894 		result->vsr_rc = VS_RESULT_CLEANED;
895 		break;
896 
897 	case VS_RESP_FORBIDDEN:
898 		/* file is infected and could not be repaired */
899 		result->vsr_rc = VS_RESULT_FORBIDDEN;
900 		break;
901 
902 	default:
903 		syslog(LOG_ERR, "ICAP protocol error "
904 		    "- unsupported scan result: %s",
905 		    vs_icap_resp_str(ctx->vsc_info.vsi_icap_rc));
906 		return (-1);
907 	}
908 
909 	return (0);
910 }
911 
912 
913 /*
914  * vs_icap_read_encap_hdr
915  *
916  * Read the encapsulated response header to determine the length of
917  * encapsulated data and, in some cases, to detect the infected state
918  * of the file.
919  *
920  * Use of http response code:
921  * Trend IWSS does not return virus information in the RESPMOD response
922  * headers unless the OPTIONAL "include X_Infection_Found" checkbox is
923  * checked and "disable_infected_url_block=yes" is set in intscan.ini.
924  * Thus if we haven't already detected the infected/cleaned status
925  * (ie if vsr_rc == VS_RESULT_CLEAN) we attempt to detect the
926  * infected/cleaned state of a file from a combination of the ICAP and
927  * http resp codes.
928  * Here are the response code values that Trend IWSS returns:
929  *  - clean:      icap resp = VS_RESP_NO_CONT_NEEDED
930  *  - quarantine: icap resp = VS_RESP_OK, http resp = VS_RESP_FORBIDDEN
931  *  - cleaned:    icap resp = VS_RESP_OK, http resp = VS_RESP_OK
932  * For all other vendors' scan engines (so far) the infected/cleaned
933  * state of the file has already been detected from the RESPMOD
934  * response headers.
935  */
936 static int
937 vs_icap_read_encap_hdr(vs_scan_ctx_t *ctx)
938 {
939 	char *buf = ctx->vsc_info.vsi_recv_buf;
940 	char *name, *value;
941 	int  retval;
942 
943 	/* Break on error or blank line. */
944 	for (;;) {
945 		if ((retval = vs_icap_readline(ctx, buf, VS_BUF_SZ)) < 0)
946 			return (-1);
947 
948 		/* Empty line (CR/LF) normal break */
949 		if ((retval == 0) || (!buf[0]))
950 			break;
951 
952 		if (MATCH(buf, "HTTP/1.1")) {
953 			(void) sscanf(buf + 8, "%d",
954 			    &ctx->vsc_info.vsi_http_rc);
955 			ctx->vsc_info.vsi_html_content = B_TRUE;
956 
957 			/* if not yet detected infection, interpret http_rc */
958 			if (ctx->vsc_result->vsr_rc == VS_RESULT_CLEAN) {
959 				if ((ctx->vsc_info.vsi_icap_rc == VS_RESP_OK) &&
960 				    (ctx->vsc_info.vsi_http_rc == VS_RESP_OK)) {
961 					ctx->vsc_result->vsr_rc =
962 					    VS_RESULT_CLEANED;
963 				} else {
964 					ctx->vsc_result->vsr_rc =
965 					    VS_RESULT_FORBIDDEN;
966 				}
967 			}
968 		} else {
969 			vs_icap_parse_hdrs(':', buf, &name, &value);
970 			if (name && (MATCH(name, "Content-Length"))) {
971 				(void) sscanf(value, "%d",
972 				    &ctx->vsc_info.vsi_content_len);
973 			}
974 		}
975 	}
976 
977 	return (0);
978 }
979 
980 
981 /*
982  * vs_icap_read_encap_data
983  *
984  * Read the encapsulated response data.
985  *
986  * If the response data represents cleaned file data (for an infected file)
987  * and VS_NO_REPAIR is not set, open repair file to save the reponse body
988  * data in. Set the repair flag in the scan context. The repair flag is used
989  * during the processing of the response data. If the flag is set then the
990  * data is written to file. If any error occurs which invalidates the repaired
991  * data file the repair flag gets reset to 0, and the data will be discarded.
992  *
993  * The result is reset to VS_RESULT_FORBIDDEN until all of the cleaned data
994  * has been successfully received and processed. It is then reset to
995  * VS_RESULT_CLEANED.
996  *
997  * If the data doesn't represent cleaned file data, or we cannot (or don't
998  * want to) write the cleaned data to file, the data is discarded (repair flag
999  * in ctx == 0).
1000  */
1001 static void
1002 vs_icap_read_encap_data(vs_scan_ctx_t *ctx)
1003 {
1004 	if (ctx->vsc_result->vsr_rc == VS_RESULT_CLEANED) {
1005 		ctx->vsc_result->vsr_rc = VS_RESULT_FORBIDDEN;
1006 
1007 		if (!(ctx->vsc_flags & VS_NO_REPAIR)) {
1008 			if (vs_icap_create_repair_file(ctx) == 0)
1009 				ctx->vsc_repair = B_TRUE;
1010 		}
1011 	}
1012 
1013 	/*
1014 	 * vs_icap_read_resp_body handles errors internally;
1015 	 * resets ctx->vsc_repair
1016 	 */
1017 	(void) vs_icap_read_resp_body(ctx);
1018 
1019 	if (ctx->vsc_repair_fd != -1) {
1020 		(void) close(ctx->vsc_repair_fd);
1021 
1022 		if (ctx->vsc_repair) {
1023 			/* repair file contains the cleaned data */
1024 			ctx->vsc_result->vsr_rc = VS_RESULT_CLEANED;
1025 		} else {
1026 			/* error occured processing data. Remove repair file */
1027 			(void) unlink(ctx->vsc_repair_fname);
1028 		}
1029 	}
1030 }
1031 
1032 
1033 /*
1034  * vs_icap_create_repair_file
1035  *
1036  * Create and open a file to save cleaned data in.
1037  */
1038 static int
1039 vs_icap_create_repair_file(vs_scan_ctx_t *ctx)
1040 {
1041 	if (ctx->vsc_repair_fname == NULL)
1042 		return (-1);
1043 
1044 	if ((ctx->vsc_repair_fd = open(ctx->vsc_repair_fname,
1045 	    O_RDWR | O_CREAT | O_EXCL | O_TRUNC, 0644)) == -1) {
1046 		return (-1);
1047 	}
1048 
1049 	return (0);
1050 }
1051 
1052 
1053 /*
1054  * vs_icap_read_resp_body
1055  *
1056  * Repeatedly call vs_icap_read_body_chunk until it returns:
1057  *    0 indicating that there's no more data to read or
1058  *   -1 indicating a read error -> reset ctx->vsc_repair 0
1059  *
1060  * Returns: 0 success
1061  *         -1 error
1062  */
1063 static int
1064 vs_icap_read_resp_body(vs_scan_ctx_t *ctx)
1065 {
1066 	int retval;
1067 
1068 	while ((retval = vs_icap_read_body_chunk(ctx)) > 0)
1069 		;
1070 
1071 	if (retval < 0)
1072 		ctx->vsc_repair = B_FALSE;
1073 
1074 	return (retval);
1075 }
1076 
1077 
1078 /*
1079  * vs_icap_read_body_chunk
1080  *
1081  * Read the chunk size, then read the chunk of data and write the
1082  * data to file repair_fd (or discard it).
1083  * If the data cannot be successfully written to file, set repair
1084  * flag in ctx to 0, and discard all subsequent data.
1085  *
1086  * Returns: chunk size
1087  *          -1 on error
1088  */
1089 static int
1090 vs_icap_read_body_chunk(vs_scan_ctx_t *ctx)
1091 {
1092 	char *lbuf = ctx->vsc_info.vsi_recv_buf;
1093 	unsigned int chunk_size, resid;
1094 	int rsize;
1095 
1096 	/* Read and parse the chunk size. */
1097 	if ((vs_icap_readline(ctx, lbuf, VS_BUF_SZ) < 0) ||
1098 	    (!sscanf(lbuf, "%x", &chunk_size))) {
1099 		return (-1);
1100 	}
1101 
1102 	/* Read and save/discard chunk */
1103 	resid = chunk_size;
1104 	while (resid) {
1105 		rsize = (resid < VS_BUF_SZ) ? resid : VS_BUF_SZ;
1106 
1107 		if ((rsize = vs_icap_read(ctx->vsc_sockfd, lbuf, rsize)) <= 0)
1108 			return (-1);
1109 
1110 		if (ctx->vsc_repair) {
1111 			if (vs_icap_write(ctx->vsc_repair_fd, lbuf, rsize) < 0)
1112 				ctx->vsc_repair = B_FALSE;
1113 		}
1114 
1115 		resid -= rsize;
1116 	}
1117 
1118 	/* Eat one CR/LF after the data */
1119 	if (vs_icap_readline(ctx, lbuf, VS_BUF_SZ) < 0)
1120 		return (-1);
1121 
1122 	if (lbuf[0]) {
1123 		syslog(LOG_ERR, "ICAP protocol error - expected blank line");
1124 		return (-1);
1125 	}
1126 
1127 	return (chunk_size);
1128 }
1129 
1130 
1131 /* *********************************************************************** */
1132 /*			Utility read, write functions			   */
1133 /* *********************************************************************** */
1134 
1135 /*
1136  * vs_icap_write
1137  *
1138  * Return: 0 if all data successfully written
1139  *        -1 otherwise
1140  */
1141 static int
1142 vs_icap_write(int fd, char *buf, int buflen)
1143 {
1144 	char *ptr = buf;
1145 	int resid = buflen;
1146 	int bytes_sent = 0;
1147 
1148 	while (resid > 0) {
1149 		errno = 0;
1150 		bytes_sent = write(fd, ptr, resid);
1151 		if (bytes_sent < 0) {
1152 			if (errno == EINTR)
1153 				continue;
1154 			else
1155 				return (-1);
1156 		}
1157 		resid -= bytes_sent;
1158 		ptr += bytes_sent;
1159 	}
1160 
1161 	return (0);
1162 }
1163 
1164 
1165 /*
1166  * vs_icap_read
1167  *
1168  * Returns: bytes_read (== len unless EOF hit before len bytes read)
1169  *          -1 error
1170  */
1171 static int
1172 vs_icap_read(int fd, char *buf, int len)
1173 {
1174 	char *ptr = buf;
1175 	int resid = len;
1176 	int bytes_read = 0;
1177 
1178 	while (resid > 0) {
1179 		errno = 0;
1180 		bytes_read = read(fd, ptr, resid);
1181 		if (bytes_read < 0) {
1182 			if (errno == EINTR)
1183 				continue;
1184 			else
1185 				return (-1);
1186 		}
1187 		resid -= bytes_read;
1188 		ptr += bytes_read;
1189 	}
1190 
1191 	return (len - resid);
1192 }
1193 
1194 
1195 /*
1196  * vs_icap_send_chunk
1197  *
1198  * Send a "chunk" of file data, containing:
1199  * - Length (in hex) CR/NL
1200  * - [optiona data]
1201  * - CR/NL
1202  *
1203  * Returns: data length sent (not including encapsulation)
1204  *          -1 - error
1205  */
1206 static int
1207 vs_icap_send_chunk(vs_scan_ctx_t *ctx, int chunk_len)
1208 {
1209 	char *hdr = ctx->vsc_info.vsi_send_hdr;
1210 	char *dbuf = ctx->vsc_info.vsi_send_buf;
1211 	char *tail;
1212 	char head[VS_HDR_SZ + 1];
1213 	int nread = 0, hlen, tlen = 2;
1214 
1215 	if (chunk_len > VS_BUF_SZ)
1216 		chunk_len = VS_BUF_SZ;
1217 
1218 	/* Read the data. */
1219 	if ((nread = vs_icap_read(ctx->vsc_fd, dbuf, chunk_len)) < 0)
1220 		return (-1);
1221 
1222 	if (nread > 0) {
1223 		/* wrap data in a header and trailer */
1224 		hlen = snprintf(head, sizeof (head), "%x\r\n", nread);
1225 		hdr += (VS_HDR_SZ - hlen);
1226 		(void) memcpy(hdr, head, hlen);
1227 		tail = dbuf + nread;
1228 		tail[0] = '\r';
1229 		tail[1] = '\n';
1230 
1231 		if (vs_icap_write(ctx->vsc_sockfd, hdr,
1232 		    hlen + nread + tlen) < 0) {
1233 			return (-1);
1234 		}
1235 	}
1236 
1237 	return (nread);
1238 }
1239 
1240 
1241 /*
1242  * vs_icap_send_termination
1243  *
1244  * Send 0 length termination to scan engine: "0\r\n\r\n"
1245  *
1246  * Returns: 0 - success
1247  *         -1 - error
1248  */
1249 static int
1250 vs_icap_send_termination(vs_scan_ctx_t *ctx)
1251 {
1252 	if (vs_icap_write(ctx->vsc_sockfd, VS_TERMINATION,
1253 	    strlen(VS_TERMINATION)) < 0) {
1254 		return (-1);
1255 	}
1256 
1257 	return (0);
1258 }
1259 
1260 
1261 /*
1262  * vs_icap_readline
1263  *
1264  * Read a line of response data from the socket. \n indicates end of line.
1265  *
1266  *  Returns: bytes read
1267  *          -1 - error
1268  */
1269 static int
1270 vs_icap_readline(vs_scan_ctx_t *ctx, char *buf, int buflen)
1271 {
1272 	char c;
1273 	int i, retval;
1274 
1275 	i = 0;
1276 	for (;;) {
1277 		errno = 0;
1278 		retval = recv(ctx->vsc_sockfd, &c, 1, 0);
1279 
1280 		if (retval < 0 && errno == EINTR)
1281 			continue;
1282 
1283 		if (retval <= 0) {
1284 			if (vscand_get_state() != VS_STATE_SHUTDOWN) {
1285 				syslog(LOG_ERR, "Error receiving data from "
1286 				    "Scan Engine: %s", strerror(errno));
1287 			}
1288 			return (-1);
1289 		}
1290 
1291 		buf[i++] = c;
1292 		if (c == '\n')
1293 			break;
1294 
1295 		if (i >= (buflen - 2))
1296 			return (-1);
1297 	}
1298 
1299 	buf[i] = '\0';
1300 
1301 	/* remove preceding and trailing whitespace */
1302 	vs_icap_trimspace(buf);
1303 
1304 	return (i);
1305 }
1306 
1307 
1308 /* ************************************************************************ */
1309 /*				HEADER processing			    */
1310 /* ************************************************************************ */
1311 
1312 /*
1313  * vs_icap_parse_hdrs
1314  *
1315  * parse an icap hdr line to find name and value
1316  */
1317 static void
1318 vs_icap_parse_hdrs(char delimiter, char *line, char **name, char **val)
1319 {
1320 	char *q = line;
1321 	int line_len;
1322 
1323 	/* strip any spaces */
1324 	while (*q == ' ')
1325 		q++;
1326 
1327 	*name = q;
1328 	*val = 0;
1329 
1330 	/* Empty line is normal termination */
1331 	if ((line_len = strlen(line)) == 0)
1332 		return;
1333 
1334 	if ((q = strchr(line, delimiter)) != 0) {
1335 		*q++ = '\0';
1336 	} else {
1337 		q = line + line_len;
1338 	}
1339 
1340 	/* value part follows spaces */
1341 	while (*q == ' ')
1342 		q++;
1343 
1344 	*val = q;
1345 }
1346 
1347 
1348 /*
1349  * vs_icap_resp_violations
1350  */
1351 /*ARGSUSED*/
1352 static int
1353 vs_icap_resp_violations(vs_scan_ctx_t *ctx, int hdr_id, char *line)
1354 {
1355 	int i, rv, vcnt;
1356 
1357 	(void) sscanf(line, "%d", &vcnt);
1358 
1359 	ctx->vsc_result->vsr_nviolations =
1360 	    (vcnt > VS_MAX_VIOLATIONS) ? VS_MAX_VIOLATIONS : vcnt;
1361 
1362 	ctx->vsc_info.vsi_threat_hdr = VS_RESP_X_VIOLATIONS;
1363 
1364 	for (i = 0; i < vcnt; i++) {
1365 		if ((rv = vs_icap_resp_violation_rec(ctx, i)) < 0)
1366 			return (rv);
1367 
1368 	}
1369 
1370 	return (1);
1371 }
1372 
1373 
1374 /*
1375  * vs_icap_resp_violation_rec
1376  *
1377  * take all violation data (up to VS_MAX_VIOLATIONS) and save it
1378  * in violation_info.
1379  * each violation has 4 lines of info: doc name, virus name,
1380  * virus id and resolution
1381  */
1382 static int
1383 vs_icap_resp_violation_rec(vs_scan_ctx_t *ctx, int vr_idx)
1384 {
1385 	int vline;
1386 	int retval = 0;
1387 	char *buf = ctx->vsc_info.vsi_recv_buf;
1388 	vs_vrec_t *vr;
1389 
1390 	if (vr_idx < VS_MAX_VIOLATIONS) {
1391 		vr = &ctx->vsc_result->vsr_vrec[vr_idx];
1392 	} else {
1393 		vr = 0;
1394 	}
1395 
1396 	for (vline = 0; vline < VS_VIOLATION_LINES; vline++) {
1397 		if ((retval = vs_icap_readline(ctx, buf, VS_BUF_SZ)) < 0)
1398 			return (-1);
1399 
1400 		/* empty line? */
1401 		if ((retval == 0) || (!buf[0]))
1402 			break;
1403 
1404 		if (vr) {
1405 			switch (vline) {
1406 			case 0: /* doc name */
1407 				break;
1408 			case 1: /* Threat Description */
1409 				(void) strlcpy(vr->vr_desc, buf,
1410 				    VS_DESCRIPTION_MAX);
1411 				break;
1412 			case 2: /* Problem ID */
1413 				(void) sscanf(buf, "%d", &vr->vr_id);
1414 				break;
1415 			case 3: /* Resolution */
1416 				(void) sscanf(buf, "%d", &vr->vr_res);
1417 				break;
1418 			}
1419 		}
1420 	}
1421 
1422 	return (1);
1423 }
1424 
1425 
1426 /*
1427  * vs_icap_opt_value
1428  * given an icap options hdr string, process value
1429  */
1430 static int
1431 vs_icap_opt_value(vs_scan_ctx_t *ctx, int hdr_id, char *line)
1432 {
1433 	int x;
1434 	long val;
1435 	char *end;
1436 
1437 	switch (hdr_id) {
1438 	case VS_OPT_PREVIEW:
1439 		(void) sscanf(line, "%d", &x);
1440 		if (x < VS_MIN_PREVIEW_LEN)
1441 			x = VS_MIN_PREVIEW_LEN;
1442 		if (x > VS_BUF_SZ)
1443 			x = VS_BUF_SZ;
1444 		ctx->vsc_options.vso_preview_len = x;
1445 		break;
1446 
1447 	case VS_OPT_TTL:
1448 		if (*line == 0) {
1449 			ctx->vsc_options.vso_req_time = -1;
1450 			break;
1451 		}
1452 
1453 		val = strtol(line, &end, 10);
1454 		if ((end != (line + strlen(line))) || (val < 0))
1455 			break;
1456 
1457 		ctx->vsc_options.vso_ttl = val;
1458 		ctx->vsc_options.vso_req_time = time(0);
1459 		break;
1460 
1461 	case VS_OPT_ALLOW:
1462 		(void) sscanf(line, "%d", &ctx->vsc_options.vso_allow);
1463 		break;
1464 
1465 	case VS_OPT_SERVICE:
1466 		(void) strlcpy(ctx->vsc_options.vso_service, line,
1467 		    VS_SERVICE_SZ);
1468 		break;
1469 
1470 	case VS_OPT_X_DEF_INFO:
1471 		(void) strlcpy(ctx->vsc_options.vso_defninfo, line,
1472 		    VS_DEFN_SZ);
1473 		break;
1474 
1475 	case VS_OPT_METHODS:
1476 		if (strstr(line, "RESPMOD") != NULL)
1477 			ctx->vsc_options.vso_respmod = 1;
1478 		break;
1479 
1480 	case VS_OPT_ISTAG:
1481 		vs_icap_istag_to_scanstamp(line,
1482 		    ctx->vsc_options.vso_scanstamp);
1483 		break;
1484 
1485 	default:
1486 		break;
1487 
1488 	}
1489 
1490 	return (1);
1491 }
1492 
1493 
1494 /*
1495  * vs_icap_resp_istag
1496  *
1497  * Called to handle ISTAG when received in RESPMOD response.
1498  *  - populate result->vsr_scanstamp from istag
1499  *  - update the scanstamp in vs_options and log the update.
1500  */
1501 /*ARGSUSED*/
1502 static int
1503 vs_icap_resp_istag(vs_scan_ctx_t *ctx, int hdr_id, char *line)
1504 {
1505 	vs_icap_istag_to_scanstamp(line, ctx->vsc_result->vsr_scanstamp);
1506 
1507 	/* update the scanstamp in vs_options */
1508 	(void) pthread_mutex_lock(&vs_opt_mutex);
1509 	if (vs_icap_compare_se(ctx->vsc_idx,
1510 	    ctx->vsc_host, ctx->vsc_port) == 0) {
1511 		if (strcmp(vs_options[ctx->vsc_idx].vso_scanstamp,
1512 		    ctx->vsc_result->vsr_scanstamp) != 0) {
1513 			(void) strlcpy(vs_options[ctx->vsc_idx].vso_scanstamp,
1514 			    ctx->vsc_result->vsr_scanstamp,
1515 			    sizeof (vs_scanstamp_t));
1516 		}
1517 	}
1518 	(void) pthread_mutex_unlock(&vs_opt_mutex);
1519 
1520 	return (1);
1521 }
1522 
1523 
1524 /*
1525  * vs_icap_istag_to_scanstamp
1526  *
1527  * Copies istag into scanstamp, stripping leading and trailing
1528  * quotes '"' from istag. If the istag is invalid (too long)
1529  * scanstamp will be left unchanged.
1530  *
1531  * vs_scanstamp_t is defined to be large enough to hold the
1532  * istag plus a null terminator.
1533  */
1534 static void
1535 vs_icap_istag_to_scanstamp(char *istag, vs_scanstamp_t scanstamp)
1536 {
1537 	char *p = istag;
1538 	int len;
1539 
1540 	/* eliminate preceding '"' */
1541 	if (p[0] == '"')
1542 		++p;
1543 
1544 	/* eliminate trailing '"' */
1545 	len = strlen(p);
1546 	if (p[len - 1] == '"')
1547 		--len;
1548 
1549 	if (len < sizeof (vs_scanstamp_t))
1550 		(void) strlcpy(scanstamp, p, len + 1);
1551 }
1552 
1553 
1554 /*
1555  * vs_icap_opt_ext
1556  *
1557  * read the transfer preview / transfer complete headers to
1558  * determine which file types can be previewed
1559  */
1560 static int
1561 vs_icap_opt_ext(vs_scan_ctx_t *ctx, int hdr_id, char *line)
1562 {
1563 	vs_options_t *opt = &ctx->vsc_options;
1564 
1565 	switch (hdr_id) {
1566 	case VS_OPT_XFER_PREVIEW:
1567 		if (opt->vso_xfer_preview) {
1568 			free(opt->vso_xfer_preview);
1569 			opt->vso_xfer_preview = 0;
1570 		}
1571 		if (strstr(line, "*")) {
1572 			opt->vso_xfer_how = VS_PREVIEW_ALL;
1573 		} else {
1574 			opt->vso_xfer_preview = vs_icap_make_strvec
1575 			    (line, EXT_SEPARATOR);
1576 			opt->vso_xfer_how = VS_PREVIEW_LIST;
1577 		}
1578 		break;
1579 
1580 	case VS_OPT_XFER_COMPLETE :
1581 		if (opt->vso_xfer_complete) {
1582 			free(opt->vso_xfer_complete);
1583 			opt->vso_xfer_complete = 0;
1584 		}
1585 		if (strstr(line, "*")) {
1586 			opt->vso_xfer_how = VS_PREVIEW_NONE;
1587 		} else {
1588 			opt->vso_xfer_complete = vs_icap_make_strvec
1589 			    (line, EXT_SEPARATOR);
1590 			opt->vso_xfer_how = VS_PREVIEW_EXCEPT;
1591 		}
1592 		break;
1593 	default:
1594 		break;
1595 	}
1596 
1597 	return (1);
1598 }
1599 
1600 
1601 /*
1602  * vs_icap_resp_infection
1603  *
1604  * read the type, resolution and threat description for each
1605  * reported violation and save in ctx->vsc_result
1606  */
1607 /*ARGSUSED*/
1608 static int
1609 vs_icap_resp_infection(vs_scan_ctx_t *ctx, int hdr_id, char *line)
1610 {
1611 	char *name, *val;
1612 	int i, got = 0;
1613 	int type = 0, res = 0;
1614 	char *desc = 0;
1615 	vs_vrec_t *vr = 0;
1616 
1617 	for (i = 0; i < VS_INFECTION_FIELDS; i++) {
1618 		vs_icap_parse_hdrs('=', line, &name, &val);
1619 
1620 		switch (i) {
1621 		case 0:
1622 			if (MATCH(name, "Type")) {
1623 				(void) sscanf(val, "%d", &type);
1624 				got++;
1625 			}
1626 			break;
1627 		case 1:
1628 			if (MATCH(name, "Resolution")) {
1629 				(void) sscanf(val, "%d", &res);
1630 				got++;
1631 			}
1632 			break;
1633 		case 2:
1634 			if (MATCH(name, "Threat")) {
1635 				desc = val;
1636 				got++;
1637 			}
1638 			break;
1639 		default :
1640 			break;
1641 		}
1642 
1643 		if ((line = strstr(val, ";")))
1644 			line++;
1645 	}
1646 
1647 	if (got != VS_INFECTION_FIELDS)
1648 		return (0);
1649 
1650 	/*
1651 	 * We may have info from an X-Violations-Found record, (which provides
1652 	 * more complete information). If so, don't destroy what we have.
1653 	 */
1654 	if ((ctx->vsc_result->vsr_nviolations == 0) ||
1655 	    (ctx->vsc_info.vsi_threat_hdr < VS_RESP_X_INFECTION)) {
1656 		vr = &ctx->vsc_result->vsr_vrec[0];
1657 		vr->vr_id = type;
1658 		vr->vr_res = res;
1659 		(void) strlcpy(vr->vr_desc, desc, VS_DESCRIPTION_MAX);
1660 		ctx->vsc_result->vsr_nviolations = 1;
1661 
1662 		ctx->vsc_info.vsi_threat_hdr = VS_RESP_X_INFECTION;
1663 	}
1664 
1665 	return (1);
1666 }
1667 
1668 
1669 /*
1670  * vs_icap_resp_virus_id
1671  *
1672  * X-Virus-ID is defined as being a shorter alternative to X-Infection-Found.
1673  * If we already have virus information, from either X-Infection-Found or
1674  * X-Violations-Found, it will be more complete, so don't overwrite it with
1675  * the info from X-Virus-ID.
1676  */
1677 /*ARGSUSED*/
1678 static int
1679 vs_icap_resp_virus_id(vs_scan_ctx_t *ctx, int hdr_id, char *line)
1680 {
1681 	vs_vrec_t *vr = 0;
1682 
1683 	if (ctx->vsc_result->vsr_nviolations == 0) {
1684 		vr = &ctx->vsc_result->vsr_vrec[0];
1685 		vr->vr_id = 0;
1686 		vr->vr_res = 0;
1687 		(void) strlcpy(vr->vr_desc, line, VS_DESCRIPTION_MAX);
1688 		ctx->vsc_result->vsr_nviolations = 1;
1689 
1690 		ctx->vsc_info.vsi_threat_hdr = VS_RESP_X_VIRUS_ID;
1691 	}
1692 
1693 	return (1);
1694 }
1695 
1696 
1697 /*
1698  * vs_icap_resp_encap
1699  *
1700  * get the encapsulated header info
1701  */
1702 /*ARGSUSED*/
1703 static int
1704 vs_icap_resp_encap(vs_scan_ctx_t *ctx, int hdr_id, char *line)
1705 {
1706 	if (strstr(line, "res-hdr"))
1707 		ctx->vsc_info.vsi_res_hdr = B_TRUE;
1708 
1709 	if (strstr(line, "res-body"))
1710 		ctx->vsc_info.vsi_res_body = B_TRUE;
1711 
1712 	return (1);
1713 }
1714 
1715 
1716 /*
1717  * Utility functions for handling OPTIONS data: vs_options_t
1718  */
1719 
1720 /*
1721  * vs_icap_compare_scanstamp
1722  * compare scanstamp with that stored for engine idx
1723  *
1724  * Returns: 0 - if equal
1725  */
1726 int
1727 vs_icap_compare_scanstamp(int idx, vs_scanstamp_t scanstamp)
1728 {
1729 	int rc;
1730 
1731 	if (!scanstamp || scanstamp[0] == '\0')
1732 		return (-1);
1733 
1734 	(void) pthread_mutex_lock(&vs_opt_mutex);
1735 	rc = strcmp(scanstamp, vs_options[idx].vso_scanstamp);
1736 	(void) pthread_mutex_unlock(&vs_opt_mutex);
1737 
1738 	return (rc);
1739 }
1740 
1741 
1742 /*
1743  * vs_icap_compare_se
1744  * compare host and port with that stored for engine idx
1745  *
1746  * Returns: 0 - if equal
1747  */
1748 static int
1749 vs_icap_compare_se(int idx, char *host, int port)
1750 {
1751 	if (vs_options[idx].vso_port != port)
1752 		return (-1);
1753 
1754 	if (strcmp(vs_options[idx].vso_host, host) != 0)
1755 		return (-1);
1756 
1757 	return (0);
1758 }
1759 
1760 
1761 /*
1762  * vs_icap_free_options
1763  *
1764  * Free dynamic parts of vs_options_t: xfer_preview, xfer_complete
1765  */
1766 static void
1767 vs_icap_free_options(vs_options_t *options)
1768 {
1769 	if (options->vso_xfer_preview)
1770 		free(options->vso_xfer_preview);
1771 
1772 	if (options->vso_xfer_complete)
1773 		free(options->vso_xfer_complete);
1774 
1775 	(void) memset(options, 0, sizeof (vs_options_t));
1776 }
1777 
1778 
1779 /*
1780  * vs_icap_copy_options
1781  */
1782 void
1783 vs_icap_copy_options(vs_options_t *to_opt, vs_options_t *from_opt)
1784 {
1785 	*to_opt = *from_opt;
1786 
1787 	if (from_opt->vso_xfer_preview) {
1788 		to_opt->vso_xfer_preview =
1789 		    vs_icap_copy_strvec(from_opt->vso_xfer_preview);
1790 	}
1791 
1792 	if (from_opt->vso_xfer_complete) {
1793 		to_opt->vso_xfer_complete =
1794 		    vs_icap_copy_strvec(from_opt->vso_xfer_complete);
1795 	}
1796 }
1797 
1798 
1799 /*
1800  * vs_icap_update_options
1801  */
1802 static void
1803 vs_icap_update_options(vs_scan_ctx_t *ctx)
1804 {
1805 	int idx = ctx->vsc_idx;
1806 
1807 	(void) pthread_mutex_lock(&vs_opt_mutex);
1808 
1809 	if (vs_icap_compare_se(idx, ctx->vsc_host, ctx->vsc_port) == 0) {
1810 		vs_icap_free_options(&vs_options[idx]);
1811 		vs_icap_copy_options(&vs_options[idx], &ctx->vsc_options);
1812 	}
1813 
1814 	(void) pthread_mutex_unlock(&vs_opt_mutex);
1815 }
1816 
1817 
1818 /*
1819  * vs_icap_make_strvec
1820  *
1821  * Populate a iovec_t from line, where line is a string of 'sep'
1822  * separated fields. Within the copy of line in the iovec_t each
1823  * field will be null terminated with leading & trailing whitespace
1824  * removed. This allows for fast searching.
1825  *
1826  * The iovec_t itself and the data it points to are allocated
1827  * as a single chunk.
1828  */
1829 static iovec_t *
1830 vs_icap_make_strvec(char *line, const char *sep)
1831 {
1832 	iovec_t *vec;
1833 	char *tmp, *ctx;
1834 	int datalen, len;
1835 
1836 	datalen = strlen(line) + 1;
1837 	len = sizeof (iovec_t) + datalen;
1838 
1839 	if ((vec = (iovec_t *)calloc(1, len)) == 0)
1840 		return (0);
1841 
1842 	vec->iov_len = len;
1843 	vec->iov_base = (char *)vec + sizeof (iovec_t);
1844 	(void) strlcpy(vec->iov_base, line, datalen);
1845 
1846 	/* tokenize data for easier searching */
1847 	for (tmp = strtok_r(vec->iov_base, sep, &ctx); tmp;
1848 	    tmp = strtok_r(0, sep, &ctx)) {
1849 	}
1850 
1851 	return (vec);
1852 }
1853 
1854 
1855 /*
1856  * vs_icap_copy_strvec
1857  *
1858  * allocate and copy strvec
1859  */
1860 static iovec_t *
1861 vs_icap_copy_strvec(iovec_t *from_vec)
1862 {
1863 	iovec_t *to_vec;
1864 
1865 	if ((to_vec = (iovec_t *)calloc(1, from_vec->iov_len)) == 0)
1866 		return (0);
1867 
1868 	bcopy(from_vec, to_vec, from_vec->iov_len);
1869 	to_vec->iov_base = (char *)to_vec + sizeof (iovec_t);
1870 
1871 	return (to_vec);
1872 }
1873 
1874 
1875 /*
1876  * vs_icap_check_ext
1877  *
1878  * Returns: 1 - if ext in strvec
1879  *          0 - otherwise
1880  */
1881 static int
1882 vs_icap_check_ext(char *ext, iovec_t *vec)
1883 {
1884 	char *p, *end = (char *)vec + vec->iov_len;
1885 
1886 	for (p = vec->iov_base;  p < end; p += strlen(p) + 1) {
1887 		if (MATCH(ext, p))
1888 			return (1);
1889 	}
1890 
1891 	return (0);
1892 }
1893 
1894 
1895 /*
1896  * vs_icap_resp_str
1897  */
1898 static char *
1899 vs_icap_resp_str(int rc)
1900 {
1901 	vs_resp_msg_t *p = icap_resp;
1902 
1903 	if (rc < 0)
1904 		rc = -rc;
1905 
1906 	while (p->vsm_rc != VS_RESP_UNKNOWN) {
1907 		if (p->vsm_rc == rc)
1908 			break;
1909 		p++;
1910 	}
1911 
1912 	return (p->vsm_msg);
1913 }
1914 
1915 
1916 /*
1917  * vs_icap_trimspace
1918  *
1919  * Trims whitespace from both the beginning and end of a string. This
1920  * function alters the string buffer in-place.
1921  *
1922  * Whitespaces found at the beginning of the string are eliminated by
1923  * moving forward the start of the string at the first non-whitespace
1924  * character.
1925  * Whitespace found at the end of the string are overwritten with nulls.
1926  *
1927  */
1928 static void
1929 vs_icap_trimspace(char *buf)
1930 {
1931 	char *p = buf;
1932 	char *q = buf;
1933 
1934 	if (buf == 0)
1935 		return;
1936 
1937 	while (*p && isspace(*p))
1938 		++p;
1939 
1940 	while ((*q = *p++) != 0)
1941 	++q;
1942 
1943 	if (q != buf) {
1944 		while ((--q, isspace(*q)) != 0)
1945 			*q = '\0';
1946 	}
1947 }
1948 
1949 
1950 /*
1951  * vs_icap_uri_encode
1952  *
1953  * Encode uri data (eg filename) in accordance with RFC 2396
1954  * 'Illegal' characters should be replaced with %hh, where hh is
1955  * the hex value of the character. For example a space would be
1956  * replaced with %20.
1957  * Filenames are all already UTF-8 encoded. Any UTF-8 octects that
1958  * are 'illegal' characters will be encoded as described above.
1959  *
1960  * Paramaters: data - string to be encoded (NULL terminated)
1961  *             buf  - output buffer (NULL terminated)
1962  *             size - size of output buffer
1963  *
1964  * Returns: strlen of encoded data on success
1965  *			-1 size on error (contents of buf undefined)
1966  */
1967 static int
1968 vs_icap_uri_encode(char *buf, int size, char *data)
1969 {
1970 	unsigned char *iptr;
1971 	char *optr = buf;
1972 	int len = strlen(data);
1973 
1974 	/* modify the data */
1975 	for (iptr = (unsigned char *)data; *iptr; iptr++) {
1976 		if (vs_icap_uri_illegal_char(*iptr)) {
1977 			if ((len += 2) >= size)
1978 				return (-1);
1979 			(void) sprintf(optr, "%%%0x", *iptr);
1980 			optr += 3;
1981 		} else {
1982 			if (len >= size)
1983 				return (-1);
1984 			*optr++ = *iptr;
1985 		}
1986 	}
1987 
1988 	*optr = '\0';
1989 	return (len);
1990 }
1991 
1992 
1993 /*
1994  * vs_icap_uri_illegal_char
1995  *
1996  * The following us-ascii characters (UTF-8 octets) are 'illegal':
1997  * < > # % " { } | \ ^ [ ] ` space, 0x01 -> 0x1F & 0x7F
1998  * All non us-ascii UTF-8 octets ( >= 0x80) are illegal.
1999  *
2000  * Returns: 1 if character is not allowed in a URI
2001  *          0 otherwise
2002  */
2003 static int
2004 vs_icap_uri_illegal_char(char c)
2005 {
2006 	static const char *uri_illegal_chars = "<>#%\" {}|\\^[]`";
2007 
2008 	/* us-ascii non printable characters or non us-ascii */
2009 	if ((c <= 0x1F) || (c >= 0x7F))
2010 		return (1);
2011 
2012 	/* us-ascii dis-allowed characters */
2013 	if (strchr(uri_illegal_chars, c))
2014 		return (1);
2015 
2016 	return (0);
2017 
2018 }
2019