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