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