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