xref: /titanic_50/usr/src/cmd/vscan/vscand/vs_icap.c (revision 380789fc80376bd1573770361cb177a08c7e3524)
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 2007 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 NAS AVA 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\n", 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 			return (-1);
1276 
1277 		if (retval <= 0)
1278 			continue;
1279 
1280 		buf[i++] = c;
1281 		if (c == '\n')
1282 			break;
1283 
1284 		if (i >= (buflen - 2))
1285 			return (-1);
1286 	}
1287 
1288 	buf[i] = '\0';
1289 
1290 	/* remove preceding and trailing whitespace */
1291 	vs_icap_trimspace(buf);
1292 
1293 	return (i);
1294 }
1295 
1296 
1297 /* ************************************************************************ */
1298 /* 				HEADER processing			    */
1299 /* ************************************************************************ */
1300 
1301 /*
1302  * vs_icap_parse_hdrs
1303  *
1304  * parse an icap hdr line to find name and value
1305  */
1306 static void
1307 vs_icap_parse_hdrs(char delimiter, char *line, char **name, char **val)
1308 {
1309 	char *q = line;
1310 	int line_len;
1311 
1312 	/* strip any spaces */
1313 	while (*q == ' ')
1314 		q++;
1315 
1316 	*name = q;
1317 	*val = 0;
1318 
1319 	/* Empty line is normal termination */
1320 	if ((line_len = strlen(line)) == 0)
1321 		return;
1322 
1323 	if ((q = strchr(line, delimiter)) != 0) {
1324 		*q++ = '\0';
1325 	} else {
1326 		q = line + line_len;
1327 	}
1328 
1329 	/* value part follows spaces */
1330 	while (*q == ' ')
1331 		q++;
1332 
1333 	*val = q;
1334 }
1335 
1336 
1337 /*
1338  * vs_icap_resp_violations
1339  */
1340 /*ARGSUSED*/
1341 static int
1342 vs_icap_resp_violations(vs_scan_ctx_t *ctx, int hdr_id, char *line)
1343 {
1344 	int i, rv, vcnt;
1345 
1346 	(void) sscanf(line, "%d", &vcnt);
1347 
1348 	ctx->vsc_result->vsr_nviolations =
1349 	    (vcnt > VS_MAX_VIOLATIONS) ? VS_MAX_VIOLATIONS : vcnt;
1350 
1351 	ctx->vsc_info.vsi_threat_hdr = VS_RESP_X_VIOLATIONS;
1352 
1353 	for (i = 0; i < vcnt; i++) {
1354 		if ((rv = vs_icap_resp_violation_rec(ctx, i)) < 0)
1355 			return (rv);
1356 
1357 	}
1358 
1359 	return (1);
1360 }
1361 
1362 
1363 /*
1364  * vs_icap_resp_violation_rec
1365  *
1366  * take all violation data (up to VS_MAX_VIOLATIONS) and save it
1367  * in violation_info.
1368  * each violation has 4 lines of info: doc name, virus name,
1369  * virus id and resolution
1370  */
1371 static int
1372 vs_icap_resp_violation_rec(vs_scan_ctx_t *ctx, int vr_idx)
1373 {
1374 	int vline;
1375 	int retval = 0;
1376 	char *buf = ctx->vsc_info.vsi_recv_buf;
1377 	vs_vrec_t *vr;
1378 
1379 	if (vr_idx < VS_MAX_VIOLATIONS) {
1380 		vr = &ctx->vsc_result->vsr_vrec[vr_idx];
1381 	} else {
1382 		vr = 0;
1383 	}
1384 
1385 	for (vline = 0; vline < VS_VIOLATION_LINES; vline++) {
1386 		if ((retval = vs_icap_readline(ctx, buf, VS_BUF_SZ)) < 0)
1387 			return (-1);
1388 
1389 		/* empty line? */
1390 		if ((retval == 0) || (!buf[0]))
1391 			break;
1392 
1393 		if (vr) {
1394 			switch (vline) {
1395 			case 0: /* doc name */
1396 				break;
1397 			case 1: /* Threat Description */
1398 				(void) strlcpy(vr->vr_desc, buf,
1399 				    VS_DESCRIPTION_MAX);
1400 				break;
1401 			case 2: /* Problem ID */
1402 				(void) sscanf(buf, "%d", &vr->vr_id);
1403 				break;
1404 			case 3: /* Resolution */
1405 				(void) sscanf(buf, "%d", &vr->vr_res);
1406 				break;
1407 			}
1408 		}
1409 	}
1410 
1411 	return (1);
1412 }
1413 
1414 
1415 /*
1416  * vs_icap_opt_value
1417  * given an icap options hdr string, process value
1418  */
1419 static int
1420 vs_icap_opt_value(vs_scan_ctx_t *ctx, int hdr_id, char *line)
1421 {
1422 	int x;
1423 	long val;
1424 	char *end;
1425 
1426 	switch (hdr_id) {
1427 	case VS_OPT_PREVIEW:
1428 		(void) sscanf(line, "%d", &x);
1429 		if (x < VS_MIN_PREVIEW_LEN)
1430 			x = VS_MIN_PREVIEW_LEN;
1431 		if (x > VS_BUF_SZ)
1432 			x = VS_BUF_SZ;
1433 		ctx->vsc_options.vso_preview_len = x;
1434 		break;
1435 
1436 	case VS_OPT_TTL:
1437 		if (*line == 0) {
1438 			ctx->vsc_options.vso_req_time = -1;
1439 			break;
1440 		}
1441 
1442 		val = strtol(line, &end, 10);
1443 		if ((end != (line + strlen(line))) || (val < 0))
1444 			break;
1445 
1446 		ctx->vsc_options.vso_ttl = val;
1447 		ctx->vsc_options.vso_req_time = time(0);
1448 		break;
1449 
1450 	case VS_OPT_ALLOW:
1451 		(void) sscanf(line, "%d", &ctx->vsc_options.vso_allow);
1452 		break;
1453 
1454 	case VS_OPT_SERVICE:
1455 		(void) strlcpy(ctx->vsc_options.vso_service, line,
1456 		    VS_SERVICE_SZ);
1457 		break;
1458 
1459 	case VS_OPT_X_DEF_INFO:
1460 		(void) strlcpy(ctx->vsc_options.vso_defninfo, line,
1461 		    VS_DEFN_SZ);
1462 		break;
1463 
1464 	case VS_OPT_METHODS:
1465 		if (strstr(line, "RESPMOD") != NULL)
1466 			ctx->vsc_options.vso_respmod = 1;
1467 		break;
1468 
1469 	case VS_OPT_ISTAG:
1470 		vs_icap_istag_to_scanstamp(line,
1471 		    ctx->vsc_options.vso_scanstamp);
1472 		break;
1473 
1474 	default:
1475 		break;
1476 
1477 	}
1478 
1479 	return (1);
1480 }
1481 
1482 
1483 /*
1484  * vs_icap_resp_istag
1485  *
1486  * Called to handle ISTAG when received in RESPMOD response.
1487  *  - populate result->vsr_scanstamp from istag
1488  *  - update the scanstamp in vs_options and log the update.
1489  */
1490 /*ARGSUSED*/
1491 static int
1492 vs_icap_resp_istag(vs_scan_ctx_t *ctx, int hdr_id, char *line)
1493 {
1494 	vs_icap_istag_to_scanstamp(line, ctx->vsc_result->vsr_scanstamp);
1495 
1496 	/* update the scanstamp in vs_options */
1497 	(void) pthread_mutex_lock(&vs_opt_mutex);
1498 	if (vs_icap_compare_se(ctx->vsc_idx,
1499 	    ctx->vsc_host, ctx->vsc_port) == 0) {
1500 		if (strcmp(vs_options[ctx->vsc_idx].vso_scanstamp,
1501 		    ctx->vsc_result->vsr_scanstamp) != 0) {
1502 			(void) strlcpy(vs_options[ctx->vsc_idx].vso_scanstamp,
1503 			    ctx->vsc_result->vsr_scanstamp,
1504 			    sizeof (vs_scanstamp_t));
1505 		}
1506 	}
1507 	(void) pthread_mutex_unlock(&vs_opt_mutex);
1508 
1509 	return (1);
1510 }
1511 
1512 
1513 /*
1514  * vs_icap_istag_to_scanstamp
1515  *
1516  * Copies istag into scanstamp, stripping leading and trailing
1517  * quotes '"' from istag. If the istag is invalid (too long)
1518  * scanstamp will be left unchanged.
1519  *
1520  * vs_scanstamp_t is defined to be large enough to hold the
1521  * istag plus a null terminator.
1522  */
1523 static void
1524 vs_icap_istag_to_scanstamp(char *istag, vs_scanstamp_t scanstamp)
1525 {
1526 	char *p = istag;
1527 	int len;
1528 
1529 	/* eliminate preceding '"' */
1530 	if (p[0] == '"')
1531 		++p;
1532 
1533 	/* eliminate trailing '"' */
1534 	len = strlen(p);
1535 	if (p[len - 1] == '"')
1536 		--len;
1537 
1538 	if (len < sizeof (vs_scanstamp_t))
1539 		(void) strlcpy(scanstamp, p, len + 1);
1540 }
1541 
1542 
1543 /*
1544  * vs_icap_opt_ext
1545  *
1546  * read the transfer preview / transfer complete headers to
1547  * determine which file types can be previewed
1548  */
1549 static int
1550 vs_icap_opt_ext(vs_scan_ctx_t *ctx, int hdr_id, char *line)
1551 {
1552 	vs_options_t *opt = &ctx->vsc_options;
1553 
1554 	switch (hdr_id) {
1555 	case VS_OPT_XFER_PREVIEW:
1556 		if (opt->vso_xfer_preview) {
1557 			free(opt->vso_xfer_preview);
1558 			opt->vso_xfer_preview = 0;
1559 		}
1560 		if (strstr(line, "*")) {
1561 			opt->vso_xfer_how = VS_PREVIEW_ALL;
1562 		} else {
1563 			opt->vso_xfer_preview = vs_icap_make_strvec
1564 			    (line, EXT_SEPARATOR);
1565 			opt->vso_xfer_how = VS_PREVIEW_LIST;
1566 		}
1567 		break;
1568 
1569 	case VS_OPT_XFER_COMPLETE :
1570 		if (opt->vso_xfer_complete) {
1571 			free(opt->vso_xfer_complete);
1572 			opt->vso_xfer_complete = 0;
1573 		}
1574 		if (strstr(line, "*")) {
1575 			opt->vso_xfer_how = VS_PREVIEW_NONE;
1576 		} else {
1577 			opt->vso_xfer_complete = vs_icap_make_strvec
1578 			    (line, EXT_SEPARATOR);
1579 			opt->vso_xfer_how = VS_PREVIEW_EXCEPT;
1580 		}
1581 		break;
1582 	default:
1583 		break;
1584 	}
1585 
1586 	return (1);
1587 }
1588 
1589 
1590 /*
1591  * vs_icap_resp_infection
1592  *
1593  * read the type, resolution and threat description for each
1594  * reported violation and save in ctx->vsc_result
1595  */
1596 /*ARGSUSED*/
1597 static int
1598 vs_icap_resp_infection(vs_scan_ctx_t *ctx, int hdr_id, char *line)
1599 {
1600 	char *name, *val;
1601 	int i, got = 0;
1602 	int type = 0, res = 0;
1603 	char *desc = 0;
1604 	vs_vrec_t *vr = 0;
1605 
1606 	for (i = 0; i < VS_INFECTION_FIELDS; i++) {
1607 		vs_icap_parse_hdrs('=', line, &name, &val);
1608 
1609 		switch (i) {
1610 		case 0:
1611 			if (MATCH(name, "Type")) {
1612 				(void) sscanf(val, "%d", &type);
1613 				got++;
1614 			}
1615 			break;
1616 		case 1:
1617 			if (MATCH(name, "Resolution")) {
1618 				(void) sscanf(val, "%d", &res);
1619 				got++;
1620 			}
1621 			break;
1622 		case 2:
1623 			if (MATCH(name, "Threat")) {
1624 				desc = val;
1625 				got++;
1626 			}
1627 			break;
1628 		default :
1629 			break;
1630 		}
1631 
1632 		if ((line = strstr(val, ";")))
1633 			line++;
1634 	}
1635 
1636 	if (got != VS_INFECTION_FIELDS)
1637 		return (0);
1638 
1639 	/*
1640 	 * We may have info from an X-Violations-Found record, (which provides
1641 	 * more complete information). If so, don't destroy what we have.
1642 	 */
1643 	if ((ctx->vsc_result->vsr_nviolations == 0) ||
1644 	    (ctx->vsc_info.vsi_threat_hdr < VS_RESP_X_INFECTION)) {
1645 		vr = &ctx->vsc_result->vsr_vrec[0];
1646 		vr->vr_id = type;
1647 		vr->vr_res = res;
1648 		(void) strlcpy(vr->vr_desc, desc, VS_DESCRIPTION_MAX);
1649 		ctx->vsc_result->vsr_nviolations = 1;
1650 
1651 		ctx->vsc_info.vsi_threat_hdr = VS_RESP_X_INFECTION;
1652 	}
1653 
1654 	return (1);
1655 }
1656 
1657 
1658 /*
1659  * vs_icap_resp_virus_id
1660  *
1661  * X-Virus-ID is defined as being a shorter alternative to X-Infection-Found.
1662  * If we already have virus information, from either X-Infection-Found or
1663  * X-Violations-Found, it will be more complete, so don't overwrite it with
1664  * the info from X-Virus-ID.
1665  */
1666 /*ARGSUSED*/
1667 static int
1668 vs_icap_resp_virus_id(vs_scan_ctx_t *ctx, int hdr_id, char *line)
1669 {
1670 	vs_vrec_t *vr = 0;
1671 
1672 	if (ctx->vsc_result->vsr_nviolations == 0) {
1673 		vr = &ctx->vsc_result->vsr_vrec[0];
1674 		vr->vr_id = 0;
1675 		vr->vr_res = 0;
1676 		(void) strlcpy(vr->vr_desc, line, VS_DESCRIPTION_MAX);
1677 		ctx->vsc_result->vsr_nviolations = 1;
1678 
1679 		ctx->vsc_info.vsi_threat_hdr = VS_RESP_X_VIRUS_ID;
1680 	}
1681 
1682 	return (1);
1683 }
1684 
1685 
1686 /*
1687  * vs_icap_resp_encap
1688  *
1689  * get the encapsulated header info
1690  */
1691 /*ARGSUSED*/
1692 static int
1693 vs_icap_resp_encap(vs_scan_ctx_t *ctx, int hdr_id, char *line)
1694 {
1695 	if (strstr(line, "res-hdr"))
1696 		ctx->vsc_info.vsi_res_hdr = B_TRUE;
1697 
1698 	if (strstr(line, "res-body"))
1699 		ctx->vsc_info.vsi_res_body = B_TRUE;
1700 
1701 	return (1);
1702 }
1703 
1704 
1705 /*
1706  * Utility functions for handling OPTIONS data: vs_options_t
1707  */
1708 
1709 /*
1710  * vs_icap_compare_scanstamp
1711  * compare scanstamp with that stored for engine idx
1712  *
1713  * Returns: 0 - if equal
1714  */
1715 int
1716 vs_icap_compare_scanstamp(int idx, vs_scanstamp_t scanstamp)
1717 {
1718 	int rc;
1719 
1720 	if (!scanstamp || scanstamp[0] == '\0')
1721 		return (-1);
1722 
1723 	(void) pthread_mutex_lock(&vs_opt_mutex);
1724 	rc = strcmp(scanstamp, vs_options[idx].vso_scanstamp);
1725 	(void) pthread_mutex_unlock(&vs_opt_mutex);
1726 
1727 	return (rc);
1728 }
1729 
1730 
1731 /*
1732  * vs_icap_compare_se
1733  * compare host and port with that stored for engine idx
1734  *
1735  * Returns: 0 - if equal
1736  */
1737 static int
1738 vs_icap_compare_se(int idx, char *host, int port)
1739 {
1740 	if (vs_options[idx].vso_port != port)
1741 		return (-1);
1742 
1743 	if (strcmp(vs_options[idx].vso_host, host) != 0)
1744 		return (-1);
1745 
1746 	return (0);
1747 }
1748 
1749 
1750 /*
1751  * vs_icap_free_options
1752  *
1753  * Free dynamic parts of vs_options_t: xfer_preview, xfer_complete
1754  */
1755 static void
1756 vs_icap_free_options(vs_options_t *options)
1757 {
1758 	if (options->vso_xfer_preview)
1759 		free(options->vso_xfer_preview);
1760 
1761 	if (options->vso_xfer_complete)
1762 		free(options->vso_xfer_complete);
1763 
1764 	(void) memset(options, 0, sizeof (vs_options_t));
1765 }
1766 
1767 
1768 /*
1769  * vs_icap_copy_options
1770  */
1771 void
1772 vs_icap_copy_options(vs_options_t *to_opt, vs_options_t *from_opt)
1773 {
1774 	*to_opt = *from_opt;
1775 
1776 	if (from_opt->vso_xfer_preview) {
1777 		to_opt->vso_xfer_preview =
1778 		    vs_icap_copy_strvec(from_opt->vso_xfer_preview);
1779 	}
1780 
1781 	if (from_opt->vso_xfer_complete) {
1782 		to_opt->vso_xfer_complete =
1783 		    vs_icap_copy_strvec(from_opt->vso_xfer_complete);
1784 	}
1785 }
1786 
1787 
1788 /*
1789  * vs_icap_update_options
1790  */
1791 static void
1792 vs_icap_update_options(vs_scan_ctx_t *ctx)
1793 {
1794 	int idx = ctx->vsc_idx;
1795 
1796 	(void) pthread_mutex_lock(&vs_opt_mutex);
1797 
1798 	if (vs_icap_compare_se(idx, ctx->vsc_host, ctx->vsc_port) == 0) {
1799 		vs_icap_free_options(&vs_options[idx]);
1800 		vs_icap_copy_options(&vs_options[idx], &ctx->vsc_options);
1801 	}
1802 
1803 	(void) pthread_mutex_unlock(&vs_opt_mutex);
1804 }
1805 
1806 
1807 /*
1808  * vs_icap_make_strvec
1809  *
1810  * Populate a iovec_t from line, where line is a string of 'sep'
1811  * separated fields. Within the copy of line in the iovec_t each
1812  * field will be null terminated with leading & trailing whitespace
1813  * removed. This allows for fast searching.
1814  *
1815  * The iovec_t itself and the data it points to are allocated
1816  * as a single chunk.
1817  */
1818 static iovec_t *
1819 vs_icap_make_strvec(char *line, const char *sep)
1820 {
1821 	iovec_t *vec;
1822 	char *tmp, *ctx;
1823 	int datalen, len;
1824 
1825 	datalen = strlen(line) + 1;
1826 	len = sizeof (iovec_t) + datalen;
1827 
1828 	if ((vec = (iovec_t *)calloc(1, len)) == 0)
1829 		return (0);
1830 
1831 	vec->iov_len = len;
1832 	vec->iov_base = (char *)vec + sizeof (iovec_t);
1833 	(void) strlcpy(vec->iov_base, line, datalen);
1834 
1835 	/* tokenize data for easier searching */
1836 	for (tmp = strtok_r(vec->iov_base, sep, &ctx); tmp;
1837 	    tmp = strtok_r(0, sep, &ctx)) {
1838 	}
1839 
1840 	return (vec);
1841 }
1842 
1843 
1844 /*
1845  * vs_icap_copy_strvec
1846  *
1847  * allocate and copy strvec
1848  */
1849 static iovec_t *
1850 vs_icap_copy_strvec(iovec_t *from_vec)
1851 {
1852 	iovec_t *to_vec;
1853 
1854 	if ((to_vec = (iovec_t *)calloc(1, from_vec->iov_len)) == 0)
1855 		return (0);
1856 
1857 	bcopy(from_vec, to_vec, from_vec->iov_len);
1858 	to_vec->iov_base = (char *)to_vec + sizeof (iovec_t);
1859 
1860 	return (to_vec);
1861 }
1862 
1863 
1864 /*
1865  * vs_icap_check_ext
1866  *
1867  * Returns: 1 - if ext in strvec
1868  *          0 - otherwise
1869  */
1870 static int
1871 vs_icap_check_ext(char *ext, iovec_t *vec)
1872 {
1873 	char *p, *end = (char *)vec + vec->iov_len;
1874 
1875 	for (p = vec->iov_base;  p < end; p += strlen(p) + 1) {
1876 		if (MATCH(ext, p))
1877 			return (1);
1878 	}
1879 
1880 	return (0);
1881 }
1882 
1883 
1884 /*
1885  * vs_icap_resp_str
1886  */
1887 static char *
1888 vs_icap_resp_str(int rc)
1889 {
1890 	vs_resp_msg_t *p = icap_resp;
1891 
1892 	if (rc < 0)
1893 		rc = -rc;
1894 
1895 	while (p->vsm_rc != VS_RESP_UNKNOWN) {
1896 		if (p->vsm_rc == rc)
1897 			break;
1898 		p++;
1899 	}
1900 
1901 	return (p->vsm_msg);
1902 }
1903 
1904 
1905 /*
1906  * vs_icap_trimspace
1907  *
1908  * Trims whitespace from both the beginning and end of a string. This
1909  * function alters the string buffer in-place.
1910  *
1911  * Whitespaces found at the beginning of the string are eliminated by
1912  * moving forward the start of the string at the first non-whitespace
1913  * character.
1914  * Whitespace found at the end of the string are overwritten with nulls.
1915  *
1916  */
1917 static void
1918 vs_icap_trimspace(char *buf)
1919 {
1920 	char *p = buf;
1921 	char *q = buf;
1922 
1923 	if (buf == 0)
1924 		return;
1925 
1926 	while (*p && isspace(*p))
1927 		++p;
1928 
1929 	while ((*q = *p++) != 0)
1930 	++q;
1931 
1932 	if (q != buf) {
1933 		while ((--q, isspace(*q)) != 0)
1934 			*q = '\0';
1935 	}
1936 }
1937 
1938 
1939 /*
1940  * vs_icap_uri_encode
1941  *
1942  * Encode uri data (eg filename) in accordance with RFC 2396
1943  * 'Illegal' characters should be replaced with %hh, where hh is
1944  * the hex value of the character. For example a space would be
1945  * replaced with %20.
1946  * Filenames are all already UTF-8 encoded. Any UTF-8 octects that
1947  * are 'illegal' characters will be encoded as described above.
1948  *
1949  * Paramaters: data - string to be encoded (NULL terminated)
1950  *             buf  - output buffer (NULL terminated)
1951  *             size - size of output buffer
1952  *
1953  * Returns: strlen of encoded data on success
1954  *			-1 size on error (contents of buf undefined)
1955  */
1956 static int
1957 vs_icap_uri_encode(char *buf, int size, char *data)
1958 {
1959 	unsigned char *iptr;
1960 	char *optr = buf;
1961 	int len = strlen(data);
1962 
1963 	/* modify the data */
1964 	for (iptr = (unsigned char *)data; *iptr; iptr++) {
1965 		if (vs_icap_uri_illegal_char(*iptr)) {
1966 			if ((len += 2) >= size)
1967 				return (-1);
1968 			(void) sprintf(optr, "%%%0x", *iptr);
1969 			optr += 3;
1970 		} else {
1971 			if (len >= size)
1972 				return (-1);
1973 			*optr++ = *iptr;
1974 		}
1975 	}
1976 
1977 	*optr = '\0';
1978 	return (len);
1979 }
1980 
1981 
1982 /*
1983  * vs_icap_uri_illegal_char
1984  *
1985  * The following us-ascii characters (UTF-8 octets) are 'illegal':
1986  * < > # % " { } | \ ^ [ ] ` space, 0x01 -> 0x1F & 0x7F
1987  * All non us-ascii UTF-8 octets ( >= 0x80) are illegal.
1988  *
1989  * Returns: 1 if character is not allowed in a URI
1990  *          0 otherwise
1991  */
1992 static int
1993 vs_icap_uri_illegal_char(char c)
1994 {
1995 	static const char *uri_illegal_chars = "<>#%\" {}|\\^[]`";
1996 
1997 	/* us-ascii non printable characters or non us-ascii */
1998 	if ((c <= 0x1F) || (c >= 0x7F))
1999 		return (1);
2000 
2001 	/* us-ascii dis-allowed characters */
2002 	if (strchr(uri_illegal_chars, c))
2003 		return (1);
2004 
2005 	return (0);
2006 
2007 }
2008