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