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 /*
23 * Copyright 2009 Sun Microsystems, Inc. All rights reserved.
24 * Use is subject to license terms.
25 */
26
27 #include <sys/stat.h>
28 #include <sys/ddi.h>
29 #include <sys/sunddi.h>
30 #include <sys/time.h>
31 #include <sys/varargs.h>
32 #include <sys/conf.h>
33 #include <sys/modctl.h>
34 #include <sys/cmn_err.h>
35 #include <sys/vnode.h>
36 #include <fs/fs_subr.h>
37 #include <sys/types.h>
38 #include <sys/file.h>
39 #include <sys/disp.h>
40 #include <sys/sdt.h>
41 #include <sys/cred.h>
42 #include <sys/list.h>
43 #include <sys/vscan.h>
44
45 #define VS_REQ_MAGIC 0x52515354 /* 'RQST' */
46
47 #define VS_REQS_DEFAULT 20000 /* pending scan requests - reql */
48 #define VS_NODES_DEFAULT 128 /* concurrent file scans */
49 #define VS_WORKERS_DEFAULT 32 /* worker threads */
50 #define VS_SCANWAIT_DEFAULT 15*60 /* seconds to wait for scan result */
51 #define VS_REQL_HANDLER_TIMEOUT 30
52 #define VS_EXT_RECURSE_DEPTH 8
53
54 /* access derived from scan result (VS_STATUS_XXX) and file attributes */
55 #define VS_ACCESS_UNDEFINED 0
56 #define VS_ACCESS_ALLOW 1 /* return 0 */
57 #define VS_ACCESS_DENY 2 /* return EACCES */
58
59 #define tolower(C) (((C) >= 'A' && (C) <= 'Z') ? (C) - 'A' + 'a' : (C))
60 #define offsetof(s, m) (size_t)(&(((s *)0)->m))
61
62 /* global variables - tunable via /etc/system */
63 uint32_t vs_reqs_max = VS_REQS_DEFAULT; /* max scan requests */
64 uint32_t vs_nodes_max = VS_NODES_DEFAULT; /* max in-progress scan requests */
65 uint32_t vs_workers = VS_WORKERS_DEFAULT; /* max workers send reqs to vscand */
66 uint32_t vs_scan_wait = VS_SCANWAIT_DEFAULT; /* secs to wait for scan result */
67
68
69 /*
70 * vscan_svc_state
71 *
72 * +-----------------+
73 * | VS_SVC_UNCONFIG |
74 * +-----------------+
75 * | ^
76 * | svc_init | svc_fini
77 * v |
78 * +-----------------+
79 * | VS_SVC_IDLE |<----|
80 * +-----------------+ |
81 * | |
82 * | svc_enable |
83 * |<----------------| |
84 * v | |
85 * +-----------------+ | |
86 * | VS_SVC_ENABLED |--| |
87 * +-----------------+ |
88 * | |
89 * | svc_disable | handler thread exit,
90 * v | all requests complete
91 * +-----------------+ |
92 * | VS_SVC_DISABLED |-----|
93 * +-----------------+
94 *
95 * svc_enable may occur when we are already in the ENABLED
96 * state if vscand has exited without clean shutdown and
97 * then reconnected within the delayed disable time period
98 * (vs_reconnect_timeout) - see vscan_drv
99 */
100
101 typedef enum {
102 VS_SVC_UNCONFIG,
103 VS_SVC_IDLE,
104 VS_SVC_ENABLED, /* service enabled and registered */
105 VS_SVC_DISABLED /* service disabled and nunregistered */
106 } vscan_svc_state_t;
107 static vscan_svc_state_t vscan_svc_state = VS_SVC_UNCONFIG;
108
109
110 /*
111 * vscan_svc_req_state
112 *
113 * When a scan request is received from the file system it is
114 * identified in or inserted into the vscan_svc_reql (INIT).
115 * If the request is asynchronous 0 is then returned to the caller.
116 * If the request is synchronous the req's refcnt is incremented
117 * and the caller waits for the request to complete.
118 * The refcnt is also incremented when the request is inserted
119 * in vscan_svc_nodes, and decremented on scan_complete.
120 *
121 * vscan_svc_handler processes requests from the request list,
122 * inserting them into vscan_svc_nodes and the task queue (QUEUED).
123 * When the task queue call back (vscan_svc_do_scan) is invoked
124 * the request transitions to IN_PROGRESS state. If the request
125 * is sucessfully sent to vscand (door_call) and the door response
126 * is SCANNING then the scan result will be received asynchronously.
127 * Although unusual, it is possible that the async response is
128 * received before the door call returns (hence the ASYNC_COMPLETE
129 * state).
130 * When the result has been determined / received,
131 * vscan_svc_scan_complete is invoked to transition the request to
132 * COMPLETE state, decrement refcnt and signal all waiting callers.
133 * When the last waiting caller has processed the result (refcnt == 0)
134 * the request is removed from vscan_svc_reql and vscan_svc_nodes
135 * and deleted.
136 *
137 * | ^
138 * | reql_insert | refcnt == 0
139 * v | (delete)
140 * +------------------------+ +---------------------+
141 * | VS_SVC_REQ_INIT | -----DISABLE----> | VS_SVC_REQ_COMPLETE |
142 * +------------------------+ +---------------------+
143 * | ^
144 * | insert_req, tq_dispatch |
145 * v |
146 * +------------------------+ |
147 * | VS_SVC_REQ_QUEUED | scan_complete
148 * +------------------------+ |
149 * | |
150 * | tq_callback (do_scan) |
151 * | |
152 * v scan not req'd, error, |
153 * +------------------------+ or door_result != SCANNING |
154 * | VS_SVC_REQ_IN_PROGRESS |----------------->-------------|
155 * +------------------------+ |
156 * | | |
157 * | | door_result == SCANNING |
158 * | v |
159 * | +---------------------------+ async result |
160 * | | VS_SVC_REQ_SCANNING |-------->---------|
161 * | +---------------------------+ |
162 * | |
163 * | async result |
164 * v |
165 * +---------------------------+ door_result = SCANNING |
166 * | VS_SVC_REQ_ASYNC_COMPLETE |-------->------------------|
167 * +---------------------------+
168 */
169 typedef enum {
170 VS_SVC_REQ_INIT,
171 VS_SVC_REQ_QUEUED,
172 VS_SVC_REQ_IN_PROGRESS,
173 VS_SVC_REQ_SCANNING,
174 VS_SVC_REQ_ASYNC_COMPLETE,
175 VS_SVC_REQ_COMPLETE
176 } vscan_svc_req_state_t;
177
178
179 /*
180 * vscan_svc_reql - the list of pending and in-progress scan requests
181 */
182 typedef struct vscan_req {
183 uint32_t vsr_magic; /* VS_REQ_MAGIC */
184 list_node_t vsr_lnode;
185 vnode_t *vsr_vp;
186 uint32_t vsr_idx; /* vscan_svc_nodes index */
187 uint32_t vsr_seqnum; /* unigue request id */
188 uint32_t vsr_refcnt;
189 kcondvar_t vsr_cv;
190 vscan_svc_req_state_t vsr_state;
191 } vscan_req_t;
192
193 static list_t vscan_svc_reql;
194
195
196 /*
197 * vscan_svc_nodes - table of files being scanned
198 *
199 * The index into this table is passed in the door call to
200 * vscand. vscand uses the idx to determine which minor node
201 * to open to read the file data. Within the kernel driver
202 * the minor device number can thus be used to identify the
203 * table index to get the appropriate vnode.
204 *
205 * Instance 0 is reserved for the daemon/driver control
206 * interface: enable/configure/disable
207 */
208 typedef struct vscan_svc_node {
209 vscan_req_t *vsn_req;
210 uint8_t vsn_quarantined;
211 uint8_t vsn_modified;
212 uint64_t vsn_size;
213 timestruc_t vsn_mtime;
214 vs_scanstamp_t vsn_scanstamp;
215 uint32_t vsn_result;
216 uint32_t vsn_access;
217 } vscan_svc_node_t;
218
219 static vscan_svc_node_t *vscan_svc_nodes;
220 static int vscan_svc_nodes_sz;
221
222
223 /* vscan_svc_taskq - queue of requests waiting to be sent to vscand */
224 static taskq_t *vscan_svc_taskq = NULL;
225
226 /* counts of entries in vscan_svc_reql, vscan_svc_nodes & vscan_svc_taskq */
227 typedef struct {
228 uint32_t vsc_reql;
229 uint32_t vsc_node;
230 uint32_t vsc_tq;
231 } vscan_svc_counts_t;
232 static vscan_svc_counts_t vscan_svc_counts;
233
234 /*
235 * vscan_svc_mutex protects the data pertaining to scan requests:
236 * request list - vscan_svc_reql
237 * node table - vscan_svc_nodes
238 */
239 static kmutex_t vscan_svc_mutex;
240
241 /* unique request id for vscand request/response correlation */
242 static uint32_t vscan_svc_seqnum = 0;
243
244 /*
245 * vscan_svc_cfg_mutex protects the configuration data:
246 * vscan_svc_config, vscan_svc_types
247 */
248 static kmutex_t vscan_svc_cfg_mutex;
249
250 /* configuration data - for virus scan exemption */
251 static vs_config_t vscan_svc_config;
252 static char *vscan_svc_types[VS_TYPES_MAX];
253
254 /* thread to insert reql entries into vscan_svc_nodes & vscan_svc_taskq */
255 static kthread_t *vscan_svc_reql_thread;
256 static kcondvar_t vscan_svc_reql_cv;
257 static vscan_req_t *vscan_svc_reql_next; /* next pending scan request */
258
259 /* local functions */
260 int vscan_svc_scan_file(vnode_t *, cred_t *, int);
261 static void vscan_svc_taskq_callback(void *);
262 static int vscan_svc_exempt_file(vnode_t *, boolean_t *);
263 static int vscan_svc_exempt_filetype(char *);
264 static int vscan_svc_match_ext(char *, char *, int);
265 static void vscan_svc_do_scan(vscan_req_t *);
266 static vs_scan_req_t *vscan_svc_populate_req(int);
267 static void vscan_svc_process_scan_result(int);
268 static void vscan_svc_scan_complete(vscan_req_t *);
269 static void vscan_svc_delete_req(vscan_req_t *);
270 static int vscan_svc_insert_req(vscan_req_t *);
271 static void vscan_svc_remove_req(int);
272 static vscan_req_t *vscan_svc_reql_find(vnode_t *);
273 static vscan_req_t *vscan_svc_reql_insert(vnode_t *);
274 static void vscan_svc_reql_remove(vscan_req_t *);
275
276 static int vscan_svc_getattr(int);
277 static int vscan_svc_setattr(int, int);
278
279 /* thread to insert reql entries into vscan_svc_nodes & vscan_svc_taskq */
280 static void vscan_svc_reql_handler(void);
281
282
283 /*
284 * vscan_svc_init
285 */
286 int
vscan_svc_init()287 vscan_svc_init()
288 {
289 if (vscan_svc_state != VS_SVC_UNCONFIG) {
290 DTRACE_PROBE1(vscan__svc__state__violation,
291 int, vscan_svc_state);
292 return (-1);
293 }
294
295 mutex_init(&vscan_svc_mutex, NULL, MUTEX_DEFAULT, NULL);
296 mutex_init(&vscan_svc_cfg_mutex, NULL, MUTEX_DEFAULT, NULL);
297 cv_init(&vscan_svc_reql_cv, NULL, CV_DEFAULT, NULL);
298
299 vscan_svc_nodes_sz = sizeof (vscan_svc_node_t) * (vs_nodes_max + 1);
300 vscan_svc_nodes = kmem_zalloc(vscan_svc_nodes_sz, KM_SLEEP);
301
302 vscan_svc_counts.vsc_reql = 0;
303 vscan_svc_counts.vsc_node = 0;
304 vscan_svc_counts.vsc_tq = 0;
305
306 vscan_svc_state = VS_SVC_IDLE;
307
308 return (0);
309 }
310
311
312 /*
313 * vscan_svc_fini
314 */
315 void
vscan_svc_fini()316 vscan_svc_fini()
317 {
318 if (vscan_svc_state != VS_SVC_IDLE) {
319 DTRACE_PROBE1(vscan__svc__state__violation,
320 int, vscan_svc_state);
321 return;
322 }
323
324 kmem_free(vscan_svc_nodes, vscan_svc_nodes_sz);
325
326 cv_destroy(&vscan_svc_reql_cv);
327 mutex_destroy(&vscan_svc_mutex);
328 mutex_destroy(&vscan_svc_cfg_mutex);
329 vscan_svc_state = VS_SVC_UNCONFIG;
330 }
331
332
333 /*
334 * vscan_svc_enable
335 */
336 int
vscan_svc_enable(void)337 vscan_svc_enable(void)
338 {
339 mutex_enter(&vscan_svc_mutex);
340
341 switch (vscan_svc_state) {
342 case VS_SVC_ENABLED:
343 /*
344 * it's possible (and okay) for vscan_svc_enable to be
345 * called when already enabled if vscand reconnects
346 * during a delayed disable
347 */
348 break;
349 case VS_SVC_IDLE:
350 list_create(&vscan_svc_reql, sizeof (vscan_req_t),
351 offsetof(vscan_req_t, vsr_lnode));
352 vscan_svc_reql_next = list_head(&vscan_svc_reql);
353
354 vscan_svc_taskq = taskq_create("vscan_taskq", vs_workers,
355 MINCLSYSPRI, 1, INT_MAX, TASKQ_DYNAMIC);
356 ASSERT(vscan_svc_taskq != NULL);
357
358 vscan_svc_reql_thread = thread_create(NULL, 0,
359 vscan_svc_reql_handler, 0, 0, &p0, TS_RUN, MINCLSYSPRI);
360 ASSERT(vscan_svc_reql_thread != NULL);
361
362 /* ready to start processing requests */
363 vscan_svc_state = VS_SVC_ENABLED;
364 fs_vscan_register(vscan_svc_scan_file);
365 break;
366 default:
367 DTRACE_PROBE1(vscan__svc__state__violation,
368 int, vscan_svc_state);
369 return (-1);
370 }
371
372 mutex_exit(&vscan_svc_mutex);
373 return (0);
374 }
375
376
377 /*
378 * vscan_svc_disable
379 *
380 * Resources allocated during vscan_svc_enable are free'd by
381 * the handler thread immediately prior to exiting
382 */
383 void
vscan_svc_disable(void)384 vscan_svc_disable(void)
385 {
386 mutex_enter(&vscan_svc_mutex);
387
388 switch (vscan_svc_state) {
389 case VS_SVC_ENABLED:
390 fs_vscan_register(NULL);
391 vscan_svc_state = VS_SVC_DISABLED;
392 cv_signal(&vscan_svc_reql_cv); /* wake handler thread */
393 break;
394 default:
395 DTRACE_PROBE1(vscan__svc__state__violation, int,
396 vscan_svc_state);
397 }
398
399 mutex_exit(&vscan_svc_mutex);
400 }
401
402
403 /*
404 * vscan_svc_in_use
405 */
406 boolean_t
vscan_svc_in_use()407 vscan_svc_in_use()
408 {
409 boolean_t in_use;
410
411 mutex_enter(&vscan_svc_mutex);
412
413 switch (vscan_svc_state) {
414 case VS_SVC_IDLE:
415 case VS_SVC_UNCONFIG:
416 in_use = B_FALSE;
417 break;
418 default:
419 in_use = B_TRUE;
420 break;
421 }
422
423 mutex_exit(&vscan_svc_mutex);
424 return (in_use);
425 }
426
427
428 /*
429 * vscan_svc_get_vnode
430 *
431 * Get the file vnode indexed by idx.
432 */
433 vnode_t *
vscan_svc_get_vnode(int idx)434 vscan_svc_get_vnode(int idx)
435 {
436 vnode_t *vp = NULL;
437
438 ASSERT(idx > 0);
439 ASSERT(idx <= vs_nodes_max);
440
441 mutex_enter(&vscan_svc_mutex);
442 if (vscan_svc_nodes[idx].vsn_req)
443 vp = vscan_svc_nodes[idx].vsn_req->vsr_vp;
444 mutex_exit(&vscan_svc_mutex);
445
446 return (vp);
447 }
448
449
450 /*
451 * vscan_svc_scan_file
452 *
453 * This function is the entry point for the file system to
454 * request that a file be virus scanned.
455 */
456 int
vscan_svc_scan_file(vnode_t * vp,cred_t * cr,int async)457 vscan_svc_scan_file(vnode_t *vp, cred_t *cr, int async)
458 {
459 int access;
460 vscan_req_t *req;
461 boolean_t allow;
462 clock_t timeout, time_left;
463
464 if ((vp == NULL) || (vp->v_path == NULL) || cr == NULL)
465 return (0);
466
467 DTRACE_PROBE2(vscan__scan__file, char *, vp->v_path, int, async);
468
469 /* check if size or type exempts file from scanning */
470 if (vscan_svc_exempt_file(vp, &allow)) {
471 if ((allow == B_TRUE) || (async != 0))
472 return (0);
473
474 return (EACCES);
475 }
476
477 mutex_enter(&vscan_svc_mutex);
478
479 if (vscan_svc_state != VS_SVC_ENABLED) {
480 DTRACE_PROBE1(vscan__svc__state__violation,
481 int, vscan_svc_state);
482 mutex_exit(&vscan_svc_mutex);
483 return (0);
484 }
485
486 /* insert (or find) request in list */
487 if ((req = vscan_svc_reql_insert(vp)) == NULL) {
488 mutex_exit(&vscan_svc_mutex);
489 cmn_err(CE_WARN, "Virus scan request list full");
490 return ((async != 0) ? 0 : EACCES);
491 }
492
493 /* asynchronous request: return 0 */
494 if (async) {
495 mutex_exit(&vscan_svc_mutex);
496 return (0);
497 }
498
499 /* synchronous scan request: wait for result */
500 ++(req->vsr_refcnt);
501 time_left = SEC_TO_TICK(vs_scan_wait);
502 while ((time_left > 0) && (req->vsr_state != VS_SVC_REQ_COMPLETE)) {
503 timeout = time_left;
504 time_left = cv_reltimedwait_sig(&(req->vsr_cv),
505 &vscan_svc_mutex, timeout, TR_CLOCK_TICK);
506 }
507
508 if (time_left == -1) {
509 cmn_err(CE_WARN, "Virus scan request timeout %s (%d) \n",
510 vp->v_path, req->vsr_seqnum);
511 DTRACE_PROBE1(vscan__scan__timeout, vscan_req_t *, req);
512 }
513
514 ASSERT(req->vsr_magic == VS_REQ_MAGIC);
515 if (vscan_svc_state == VS_SVC_DISABLED)
516 access = VS_ACCESS_ALLOW;
517 else if (req->vsr_idx == 0)
518 access = VS_ACCESS_DENY;
519 else
520 access = vscan_svc_nodes[req->vsr_idx].vsn_access;
521
522 if ((--req->vsr_refcnt) == 0)
523 vscan_svc_delete_req(req);
524
525 mutex_exit(&vscan_svc_mutex);
526 return ((access == VS_ACCESS_ALLOW) ? 0 : EACCES);
527 }
528
529
530 /*
531 * vscan_svc_reql_handler
532 *
533 * inserts scan requests (from vscan_svc_reql) into
534 * vscan_svc_nodes and vscan_svc_taskq
535 */
536 static void
vscan_svc_reql_handler(void)537 vscan_svc_reql_handler(void)
538 {
539 vscan_req_t *req, *next;
540
541 for (;;) {
542 mutex_enter(&vscan_svc_mutex);
543
544 if ((vscan_svc_state == VS_SVC_DISABLED) &&
545 (vscan_svc_counts.vsc_reql == 0)) {
546 /* free resources allocated durining enable */
547 taskq_destroy(vscan_svc_taskq);
548 vscan_svc_taskq = NULL;
549 list_destroy(&vscan_svc_reql);
550 vscan_svc_state = VS_SVC_IDLE;
551 mutex_exit(&vscan_svc_mutex);
552 return;
553 }
554
555 /*
556 * If disabled, scan_complete any pending requests.
557 * Otherwise insert pending requests into vscan_svc_nodes
558 * and vscan_svc_taskq. If no slots are available in
559 * vscan_svc_nodes break loop and wait for one
560 */
561 req = vscan_svc_reql_next;
562
563 while (req != NULL) {
564 ASSERT(req->vsr_magic == VS_REQ_MAGIC);
565 next = list_next(&vscan_svc_reql, req);
566
567 if (vscan_svc_state == VS_SVC_DISABLED) {
568 vscan_svc_scan_complete(req);
569 } else {
570 /* insert request into vscan_svc_nodes */
571 if (vscan_svc_insert_req(req) == -1)
572 break;
573
574 /* add the scan request into the taskq */
575 (void) taskq_dispatch(vscan_svc_taskq,
576 vscan_svc_taskq_callback,
577 (void *)req, TQ_SLEEP);
578 ++(vscan_svc_counts.vsc_tq);
579
580 req->vsr_state = VS_SVC_REQ_QUEUED;
581 }
582 req = next;
583 }
584
585 vscan_svc_reql_next = req;
586
587 DTRACE_PROBE2(vscan__req__counts, char *, "handler wait",
588 vscan_svc_counts_t *, &vscan_svc_counts);
589
590 (void) cv_reltimedwait(&vscan_svc_reql_cv, &vscan_svc_mutex,
591 SEC_TO_TICK(VS_REQL_HANDLER_TIMEOUT), TR_CLOCK_TICK);
592
593 DTRACE_PROBE2(vscan__req__counts, char *, "handler wake",
594 vscan_svc_counts_t *, &vscan_svc_counts);
595
596 mutex_exit(&vscan_svc_mutex);
597 }
598 }
599
600
601 static void
vscan_svc_taskq_callback(void * data)602 vscan_svc_taskq_callback(void *data)
603 {
604 vscan_req_t *req;
605
606 mutex_enter(&vscan_svc_mutex);
607
608 req = (vscan_req_t *)data;
609 ASSERT(req->vsr_magic == VS_REQ_MAGIC);
610 vscan_svc_do_scan(req);
611 if (req->vsr_state != VS_SVC_REQ_SCANNING)
612 vscan_svc_scan_complete(req);
613
614 --(vscan_svc_counts.vsc_tq);
615 mutex_exit(&vscan_svc_mutex);
616 }
617
618
619 /*
620 * vscan_svc_do_scan
621 *
622 * Note: To avoid potential deadlock it is important that
623 * vscan_svc_mutex is not held during the call to
624 * vscan_drv_create_note. vscan_drv_create_note enters
625 * the vscan_drv_mutex and it is possible that a thread
626 * holding that mutex could be waiting for vscan_svc_mutex.
627 */
628 static void
vscan_svc_do_scan(vscan_req_t * req)629 vscan_svc_do_scan(vscan_req_t *req)
630 {
631 int idx, result;
632 vscan_svc_node_t *node;
633 vs_scan_req_t *door_req;
634
635 ASSERT(MUTEX_HELD(&vscan_svc_mutex));
636
637 idx = req->vsr_idx;
638 node = &vscan_svc_nodes[idx];
639
640 req->vsr_state = VS_SVC_REQ_IN_PROGRESS;
641
642 /* if vscan not enabled (shutting down), allow ACCESS */
643 if (vscan_svc_state != VS_SVC_ENABLED) {
644 node->vsn_access = VS_ACCESS_ALLOW;
645 return;
646 }
647
648 if (vscan_svc_getattr(idx) != 0) {
649 cmn_err(CE_WARN, "Can't access xattr for %s\n",
650 req->vsr_vp->v_path);
651 node->vsn_access = VS_ACCESS_DENY;
652 return;
653 }
654
655 /* valid scan_req ptr guaranteed */
656 door_req = vscan_svc_populate_req(idx);
657
658 /* free up mutex around create node and door call */
659 mutex_exit(&vscan_svc_mutex);
660 if (vscan_drv_create_node(idx) != B_TRUE)
661 result = VS_STATUS_ERROR;
662 else
663 result = vscan_door_scan_file(door_req);
664 kmem_free(door_req, sizeof (vs_scan_req_t));
665 mutex_enter(&vscan_svc_mutex);
666
667 if (result != VS_STATUS_SCANNING) {
668 vscan_svc_nodes[idx].vsn_result = result;
669 vscan_svc_process_scan_result(idx);
670 } else { /* async response */
671 if (req->vsr_state == VS_SVC_REQ_IN_PROGRESS)
672 req->vsr_state = VS_SVC_REQ_SCANNING;
673 }
674 }
675
676
677 /*
678 * vscan_svc_populate_req
679 *
680 * Allocate a scan request to be sent to vscand, populating it
681 * from the data in vscan_svc_nodes[idx].
682 *
683 * Returns: scan request object
684 */
685 static vs_scan_req_t *
vscan_svc_populate_req(int idx)686 vscan_svc_populate_req(int idx)
687 {
688 vs_scan_req_t *scan_req;
689 vscan_req_t *req;
690 vscan_svc_node_t *node;
691
692 ASSERT(MUTEX_HELD(&vscan_svc_mutex));
693
694 node = &vscan_svc_nodes[idx];
695 req = node->vsn_req;
696 scan_req = kmem_zalloc(sizeof (vs_scan_req_t), KM_SLEEP);
697
698 scan_req->vsr_idx = idx;
699 scan_req->vsr_seqnum = req->vsr_seqnum;
700 (void) strncpy(scan_req->vsr_path, req->vsr_vp->v_path, MAXPATHLEN);
701 scan_req->vsr_size = node->vsn_size;
702 scan_req->vsr_modified = node->vsn_modified;
703 scan_req->vsr_quarantined = node->vsn_quarantined;
704 scan_req->vsr_flags = 0;
705 (void) strncpy(scan_req->vsr_scanstamp,
706 node->vsn_scanstamp, sizeof (vs_scanstamp_t));
707
708 return (scan_req);
709 }
710
711
712 /*
713 * vscan_svc_scan_complete
714 */
715 static void
vscan_svc_scan_complete(vscan_req_t * req)716 vscan_svc_scan_complete(vscan_req_t *req)
717 {
718 ASSERT(MUTEX_HELD(&vscan_svc_mutex));
719 ASSERT(req != NULL);
720
721 req->vsr_state = VS_SVC_REQ_COMPLETE;
722
723 if ((--req->vsr_refcnt) == 0)
724 vscan_svc_delete_req(req);
725 else
726 cv_broadcast(&(req->vsr_cv));
727 }
728
729
730 /*
731 * vscan_svc_delete_req
732 */
733 static void
vscan_svc_delete_req(vscan_req_t * req)734 vscan_svc_delete_req(vscan_req_t *req)
735 {
736 int idx;
737
738 ASSERT(MUTEX_HELD(&vscan_svc_mutex));
739 ASSERT(req != NULL);
740 ASSERT(req->vsr_refcnt == 0);
741 ASSERT(req->vsr_state == VS_SVC_REQ_COMPLETE);
742
743 if ((idx = req->vsr_idx) != 0)
744 vscan_svc_remove_req(idx);
745
746 vscan_svc_reql_remove(req);
747
748 cv_signal(&vscan_svc_reql_cv);
749 }
750
751
752 /*
753 * vscan_svc_scan_result
754 *
755 * Invoked from vscan_drv.c on receipt of an ioctl containing
756 * an async scan result (VS_DRV_IOCTL_RESULT)
757 * If the vsr_seqnum in the response does not match that in the
758 * vscan_svc_nodes entry the result is discarded.
759 */
760 void
vscan_svc_scan_result(vs_scan_rsp_t * scan_rsp)761 vscan_svc_scan_result(vs_scan_rsp_t *scan_rsp)
762 {
763 vscan_req_t *req;
764 vscan_svc_node_t *node;
765
766 mutex_enter(&vscan_svc_mutex);
767
768 node = &vscan_svc_nodes[scan_rsp->vsr_idx];
769
770 if ((req = node->vsn_req) == NULL) {
771 mutex_exit(&vscan_svc_mutex);
772 return;
773 }
774
775 ASSERT(req->vsr_magic == VS_REQ_MAGIC);
776
777 if (scan_rsp->vsr_seqnum != req->vsr_seqnum) {
778 mutex_exit(&vscan_svc_mutex);
779 return;
780 }
781
782 node->vsn_result = scan_rsp->vsr_result;
783 (void) strncpy(node->vsn_scanstamp,
784 scan_rsp->vsr_scanstamp, sizeof (vs_scanstamp_t));
785
786 vscan_svc_process_scan_result(scan_rsp->vsr_idx);
787
788 if (node->vsn_req->vsr_state == VS_SVC_REQ_SCANNING)
789 vscan_svc_scan_complete(node->vsn_req);
790 else
791 node->vsn_req->vsr_state = VS_SVC_REQ_ASYNC_COMPLETE;
792
793 mutex_exit(&vscan_svc_mutex);
794 }
795
796
797 /*
798 * vscan_svc_scan_abort
799 *
800 * Abort in-progress scan requests.
801 */
802 void
vscan_svc_scan_abort()803 vscan_svc_scan_abort()
804 {
805 int idx;
806 vscan_req_t *req;
807
808 mutex_enter(&vscan_svc_mutex);
809
810 for (idx = 1; idx <= vs_nodes_max; idx++) {
811 if ((req = vscan_svc_nodes[idx].vsn_req) == NULL)
812 continue;
813
814 ASSERT(req->vsr_magic == VS_REQ_MAGIC);
815
816 if (req->vsr_state == VS_SVC_REQ_SCANNING) {
817 DTRACE_PROBE1(vscan__abort, vscan_req_t *, req);
818 vscan_svc_process_scan_result(idx);
819 vscan_svc_scan_complete(req);
820 }
821 }
822
823 mutex_exit(&vscan_svc_mutex);
824 }
825
826
827 /*
828 * vscan_svc_process_scan_result
829 *
830 * Sets vsn_access and updates file attributes based on vsn_result,
831 * as follows:
832 *
833 * VS_STATUS_INFECTED
834 * deny access, set quarantine attribute, clear scanstamp
835 * VS_STATUS_CLEAN
836 * allow access, set scanstamp,
837 * if file not modified since scan initiated, clear modified attribute
838 * VS_STATUS_NO_SCAN
839 * deny access if file quarantined, otherwise allow access
840 * VS_STATUS_UNDEFINED, VS_STATUS_ERROR
841 * deny access if file quarantined, modified or no scanstamp
842 * otherwise, allow access
843 */
844 static void
vscan_svc_process_scan_result(int idx)845 vscan_svc_process_scan_result(int idx)
846 {
847 struct vattr attr;
848 vnode_t *vp;
849 timestruc_t *mtime;
850 vscan_svc_node_t *node;
851
852 ASSERT(MUTEX_HELD(&vscan_svc_mutex));
853
854 node = &vscan_svc_nodes[idx];
855
856 switch (node->vsn_result) {
857 case VS_STATUS_INFECTED:
858 node->vsn_access = VS_ACCESS_DENY;
859 node->vsn_quarantined = 1;
860 node->vsn_scanstamp[0] = '\0';
861 (void) vscan_svc_setattr(idx,
862 XAT_AV_QUARANTINED | XAT_AV_SCANSTAMP);
863 break;
864
865 case VS_STATUS_CLEAN:
866 node->vsn_access = VS_ACCESS_ALLOW;
867
868 /* if mtime has changed, don't clear the modified attribute */
869 vp = node->vsn_req->vsr_vp;
870 mtime = &(node->vsn_mtime);
871 attr.va_mask = AT_MTIME;
872 if ((VOP_GETATTR(vp, &attr, 0, kcred, NULL) != 0) ||
873 (mtime->tv_sec != attr.va_mtime.tv_sec) ||
874 (mtime->tv_nsec != attr.va_mtime.tv_nsec)) {
875 DTRACE_PROBE1(vscan__mtime__changed, vscan_svc_node_t *,
876 node);
877 (void) vscan_svc_setattr(idx, XAT_AV_SCANSTAMP);
878 break;
879 }
880
881 node->vsn_modified = 0;
882 (void) vscan_svc_setattr(idx,
883 XAT_AV_SCANSTAMP | XAT_AV_MODIFIED);
884 break;
885
886 case VS_STATUS_NO_SCAN:
887 if (node->vsn_quarantined)
888 node->vsn_access = VS_ACCESS_DENY;
889 else
890 node->vsn_access = VS_ACCESS_ALLOW;
891 break;
892
893 case VS_STATUS_ERROR:
894 case VS_STATUS_UNDEFINED:
895 default:
896 if ((node->vsn_quarantined) ||
897 (node->vsn_modified) ||
898 (node->vsn_scanstamp[0] == '\0'))
899 node->vsn_access = VS_ACCESS_DENY;
900 else
901 node->vsn_access = VS_ACCESS_ALLOW;
902 break;
903 }
904
905 DTRACE_PROBE4(vscan__result,
906 int, idx, int, node->vsn_req->vsr_seqnum,
907 int, node->vsn_result, int, node->vsn_access);
908 }
909
910
911 /*
912 * vscan_svc_getattr
913 *
914 * Get the vscan related system attributes, AT_SIZE & AT_MTIME.
915 */
916 static int
vscan_svc_getattr(int idx)917 vscan_svc_getattr(int idx)
918 {
919 xvattr_t xvattr;
920 xoptattr_t *xoap = NULL;
921 vnode_t *vp;
922 vscan_svc_node_t *node;
923
924 ASSERT(MUTEX_HELD(&vscan_svc_mutex));
925
926 node = &vscan_svc_nodes[idx];
927 if ((vp = node->vsn_req->vsr_vp) == NULL)
928 return (-1);
929
930 /* get the attributes */
931 xva_init(&xvattr); /* sets AT_XVATTR */
932
933 xvattr.xva_vattr.va_mask |= AT_SIZE;
934 xvattr.xva_vattr.va_mask |= AT_MTIME;
935 XVA_SET_REQ(&xvattr, XAT_AV_MODIFIED);
936 XVA_SET_REQ(&xvattr, XAT_AV_QUARANTINED);
937 XVA_SET_REQ(&xvattr, XAT_AV_SCANSTAMP);
938
939 if (VOP_GETATTR(vp, (vattr_t *)&xvattr, 0, kcred, NULL) != 0)
940 return (-1);
941
942 if ((xoap = xva_getxoptattr(&xvattr)) == NULL) {
943 cmn_err(CE_NOTE, "Virus scan request failed; "
944 "file system does not support virus scanning");
945 return (-1);
946 }
947
948 node->vsn_size = xvattr.xva_vattr.va_size;
949 node->vsn_mtime.tv_sec = xvattr.xva_vattr.va_mtime.tv_sec;
950 node->vsn_mtime.tv_nsec = xvattr.xva_vattr.va_mtime.tv_nsec;
951
952 if (XVA_ISSET_RTN(&xvattr, XAT_AV_MODIFIED) == 0)
953 return (-1);
954 node->vsn_modified = xoap->xoa_av_modified;
955
956 if (XVA_ISSET_RTN(&xvattr, XAT_AV_QUARANTINED) == 0)
957 return (-1);
958 node->vsn_quarantined = xoap->xoa_av_quarantined;
959
960 if (XVA_ISSET_RTN(&xvattr, XAT_AV_SCANSTAMP) != 0) {
961 (void) memcpy(node->vsn_scanstamp,
962 xoap->xoa_av_scanstamp, AV_SCANSTAMP_SZ);
963 }
964
965 DTRACE_PROBE1(vscan__getattr, vscan_svc_node_t *, node);
966 return (0);
967 }
968
969
970 /*
971 * vscan_svc_setattr
972 *
973 * Set the vscan related system attributes.
974 */
975 static int
vscan_svc_setattr(int idx,int which)976 vscan_svc_setattr(int idx, int which)
977 {
978 xvattr_t xvattr;
979 xoptattr_t *xoap = NULL;
980 vnode_t *vp;
981 int len;
982 vscan_svc_node_t *node;
983
984 ASSERT(MUTEX_HELD(&vscan_svc_mutex));
985
986 node = &vscan_svc_nodes[idx];
987 if ((vp = node->vsn_req->vsr_vp) == NULL)
988 return (-1);
989
990 /* update the attributes */
991 xva_init(&xvattr); /* sets AT_XVATTR */
992 if ((xoap = xva_getxoptattr(&xvattr)) == NULL)
993 return (-1);
994
995 if (which & XAT_AV_MODIFIED) {
996 XVA_SET_REQ(&xvattr, XAT_AV_MODIFIED);
997 xoap->xoa_av_modified = node->vsn_modified;
998 }
999
1000 if (which & XAT_AV_QUARANTINED) {
1001 XVA_SET_REQ(&xvattr, XAT_AV_QUARANTINED);
1002 xoap->xoa_av_quarantined = node->vsn_quarantined;
1003 }
1004
1005 if (which & XAT_AV_SCANSTAMP) {
1006 XVA_SET_REQ(&xvattr, XAT_AV_SCANSTAMP);
1007 len = strlen(node->vsn_scanstamp);
1008 (void) memcpy(xoap->xoa_av_scanstamp,
1009 node->vsn_scanstamp, len);
1010 }
1011
1012 /* if access is denied, set mtime to invalidate client cache */
1013 if (node->vsn_access != VS_ACCESS_ALLOW) {
1014 xvattr.xva_vattr.va_mask |= AT_MTIME;
1015 gethrestime(&xvattr.xva_vattr.va_mtime);
1016 }
1017
1018 if (VOP_SETATTR(vp, (vattr_t *)&xvattr, 0, kcred, NULL) != 0)
1019 return (-1);
1020
1021 DTRACE_PROBE2(vscan__setattr,
1022 vscan_svc_node_t *, node, int, which);
1023
1024 return (0);
1025 }
1026
1027
1028 /*
1029 * vscan_svc_configure
1030 *
1031 * store configuration in vscan_svc_config
1032 * set up vscan_svc_types array of pointers into
1033 * vscan_svc_config.vsc_types for efficient searching
1034 */
1035 int
vscan_svc_configure(vs_config_t * conf)1036 vscan_svc_configure(vs_config_t *conf)
1037 {
1038 int count = 0;
1039 char *p, *beg, *end;
1040
1041 mutex_enter(&vscan_svc_cfg_mutex);
1042
1043 vscan_svc_config = *conf;
1044
1045 (void) memset(vscan_svc_types, 0, sizeof (vscan_svc_types));
1046
1047 beg = vscan_svc_config.vsc_types;
1048 end = beg + vscan_svc_config.vsc_types_len;
1049
1050 for (p = beg; p < end; p += strlen(p) + 1) {
1051 if (count >= VS_TYPES_MAX) {
1052 mutex_exit(&vscan_svc_mutex);
1053 return (-1);
1054 }
1055
1056 vscan_svc_types[count] = p;
1057 ++count;
1058 }
1059
1060 mutex_exit(&vscan_svc_cfg_mutex);
1061 return (0);
1062 }
1063
1064
1065 /*
1066 * vscan_svc_exempt_file
1067 *
1068 * check if a file's size or type exempts it from virus scanning
1069 *
1070 * If the file is exempt from virus scanning, allow will be set
1071 * to define whether files access should be allowed (B_TRUE) or
1072 * denied (B_FALSE)
1073 *
1074 * Returns: 1 exempt
1075 * 0 scan required
1076 */
1077 static int
vscan_svc_exempt_file(vnode_t * vp,boolean_t * allow)1078 vscan_svc_exempt_file(vnode_t *vp, boolean_t *allow)
1079 {
1080 struct vattr attr;
1081
1082 ASSERT(vp != NULL);
1083 ASSERT(vp->v_path != NULL);
1084
1085 attr.va_mask = AT_SIZE;
1086
1087 if (VOP_GETATTR(vp, &attr, 0, kcred, NULL) != 0) {
1088 *allow = B_FALSE;
1089 return (0);
1090 }
1091
1092 mutex_enter(&vscan_svc_cfg_mutex);
1093
1094 if (attr.va_size > vscan_svc_config.vsc_max_size) {
1095 DTRACE_PROBE2(vscan__exempt__filesize, char *,
1096 vp->v_path, int, *allow);
1097
1098 *allow = (vscan_svc_config.vsc_allow) ? B_TRUE : B_FALSE;
1099 mutex_exit(&vscan_svc_cfg_mutex);
1100 return (1);
1101 }
1102
1103 if (vscan_svc_exempt_filetype(vp->v_path)) {
1104 DTRACE_PROBE1(vscan__exempt__filetype, char *, vp->v_path);
1105 *allow = B_TRUE;
1106 mutex_exit(&vscan_svc_cfg_mutex);
1107 return (1);
1108 }
1109
1110 mutex_exit(&vscan_svc_cfg_mutex);
1111 return (0);
1112 }
1113
1114
1115 /*
1116 * vscan_svc_exempt_filetype
1117 *
1118 * Each entry in vscan_svc_types includes a rule indicator (+,-)
1119 * followed by the match string for file types to which the rule
1120 * applies. Look for first match of file type in vscan_svc_types
1121 * and return 1 (exempt) if the indicator is '-', and 0 (not exempt)
1122 * if the indicator is '+'.
1123 * If vscan_svc_match_ext fails, or no match is found, return 0
1124 * (not exempt)
1125 *
1126 * Returns 1: exempt, 0: not exempt
1127 */
1128 static int
vscan_svc_exempt_filetype(char * filepath)1129 vscan_svc_exempt_filetype(char *filepath)
1130 {
1131 int i, rc, exempt = 0;
1132 char *filename, *ext;
1133
1134 ASSERT(MUTEX_HELD(&vscan_svc_cfg_mutex));
1135
1136 if ((filename = strrchr(filepath, '/')) == 0)
1137 filename = filepath;
1138 else
1139 filename++;
1140
1141 if ((ext = strrchr(filename, '.')) == NULL)
1142 ext = "";
1143 else
1144 ext++;
1145
1146 for (i = 0; i < VS_TYPES_MAX; i ++) {
1147 if (vscan_svc_types[i] == 0)
1148 break;
1149
1150 rc = vscan_svc_match_ext(vscan_svc_types[i] + 1, ext, 1);
1151 if (rc == -1)
1152 break;
1153 if (rc > 0) {
1154 DTRACE_PROBE2(vscan__type__match, char *, ext,
1155 char *, vscan_svc_types[i]);
1156 exempt = (vscan_svc_types[i][0] == '-');
1157 break;
1158 }
1159 }
1160
1161 return (exempt);
1162 }
1163
1164
1165 /*
1166 * vscan_svc_match_ext
1167 *
1168 * Performs a case-insensitive match for two strings. The first string
1169 * argument can contain the wildcard characters '?' and '*'
1170 *
1171 * Returns: 0 no match
1172 * 1 match
1173 * -1 recursion error
1174 */
1175 static int
vscan_svc_match_ext(char * patn,char * str,int depth)1176 vscan_svc_match_ext(char *patn, char *str, int depth)
1177 {
1178 int c1, c2;
1179 if (depth > VS_EXT_RECURSE_DEPTH)
1180 return (-1);
1181
1182 for (;;) {
1183 switch (*patn) {
1184 case 0:
1185 return (*str == 0);
1186
1187 case '?':
1188 if (*str != 0) {
1189 str++;
1190 patn++;
1191 continue;
1192 }
1193 return (0);
1194
1195 case '*':
1196 patn++;
1197 if (*patn == 0)
1198 return (1);
1199
1200 while (*str) {
1201 if (vscan_svc_match_ext(patn, str, depth + 1))
1202 return (1);
1203 str++;
1204 }
1205 return (0);
1206
1207 default:
1208 if (*str != *patn) {
1209 c1 = *str;
1210 c2 = *patn;
1211
1212 c1 = tolower(c1);
1213 c2 = tolower(c2);
1214 if (c1 != c2)
1215 return (0);
1216 }
1217 str++;
1218 patn++;
1219 continue;
1220 }
1221 }
1222 /* NOT REACHED */
1223 }
1224
1225
1226 /*
1227 * vscan_svc_insert_req
1228 *
1229 * Insert request in next available available slot in vscan_svc_nodes
1230 *
1231 * Returns: idx of slot, or -1 if no slot available
1232 */
1233 static int
vscan_svc_insert_req(vscan_req_t * req)1234 vscan_svc_insert_req(vscan_req_t *req)
1235 {
1236 int idx;
1237 vscan_svc_node_t *node;
1238
1239 ASSERT(MUTEX_HELD(&vscan_svc_mutex));
1240
1241 if (vscan_svc_counts.vsc_node == vs_nodes_max)
1242 return (-1);
1243
1244 for (idx = 1; idx <= vs_nodes_max; idx++) {
1245 if (vscan_svc_nodes[idx].vsn_req == NULL) {
1246 req->vsr_idx = idx;
1247
1248 node = &vscan_svc_nodes[idx];
1249 (void) memset(node, 0, sizeof (vscan_svc_node_t));
1250 node->vsn_req = req;
1251 node->vsn_modified = 1;
1252 node->vsn_result = VS_STATUS_UNDEFINED;
1253 node->vsn_access = VS_ACCESS_UNDEFINED;
1254
1255 ++(vscan_svc_counts.vsc_node);
1256 return (idx);
1257 }
1258 }
1259
1260 return (-1);
1261 }
1262
1263
1264 /*
1265 * vscan_svc_remove_req
1266 */
1267 static void
vscan_svc_remove_req(int idx)1268 vscan_svc_remove_req(int idx)
1269 {
1270 ASSERT(MUTEX_HELD(&vscan_svc_mutex));
1271
1272 if (idx != 0) {
1273 (void) memset(&vscan_svc_nodes[idx], 0,
1274 sizeof (vscan_svc_node_t));
1275 --(vscan_svc_counts.vsc_node);
1276 }
1277 }
1278
1279
1280 /*
1281 * vscan_svc_reql_find
1282 */
1283 static vscan_req_t *
vscan_svc_reql_find(vnode_t * vp)1284 vscan_svc_reql_find(vnode_t *vp)
1285 {
1286 vscan_req_t *req;
1287 ASSERT(MUTEX_HELD(&vscan_svc_mutex));
1288
1289 req = list_head(&vscan_svc_reql);
1290
1291 while (req != NULL) {
1292 ASSERT(req->vsr_magic == VS_REQ_MAGIC);
1293 if ((req->vsr_vp == vp) &&
1294 (req->vsr_state != VS_SVC_REQ_COMPLETE))
1295 break;
1296
1297 req = list_next(&vscan_svc_reql, req);
1298 }
1299
1300 return (req);
1301 }
1302
1303
1304 /*
1305 * vscan_svc_reql_insert
1306 */
1307 static vscan_req_t *
vscan_svc_reql_insert(vnode_t * vp)1308 vscan_svc_reql_insert(vnode_t *vp)
1309 {
1310 vscan_req_t *req;
1311
1312 ASSERT(MUTEX_HELD(&vscan_svc_mutex));
1313
1314 /* if request already in list then return it */
1315 if ((req = vscan_svc_reql_find(vp)) != NULL)
1316 return (req);
1317
1318 /* if list is full return NULL */
1319 if (vscan_svc_counts.vsc_reql == vs_reqs_max)
1320 return (NULL);
1321
1322 /* create a new request and insert into list */
1323 VN_HOLD(vp);
1324
1325 req = kmem_zalloc(sizeof (vscan_req_t), KM_SLEEP);
1326
1327 req->vsr_magic = VS_REQ_MAGIC;
1328 if (vscan_svc_seqnum == UINT32_MAX)
1329 vscan_svc_seqnum = 0;
1330 req->vsr_seqnum = ++vscan_svc_seqnum;
1331 req->vsr_vp = vp;
1332 req->vsr_refcnt = 1; /* decremented in vscan_svc_scan_complete */
1333 req->vsr_state = VS_SVC_REQ_INIT;
1334 cv_init(&(req->vsr_cv), NULL, CV_DEFAULT, NULL);
1335
1336 list_insert_tail(&vscan_svc_reql, req);
1337 if (vscan_svc_reql_next == NULL)
1338 vscan_svc_reql_next = req;
1339
1340 ++(vscan_svc_counts.vsc_reql);
1341
1342 /* wake reql handler thread */
1343 cv_signal(&vscan_svc_reql_cv);
1344
1345 return (req);
1346 }
1347
1348
1349 /*
1350 * vscan_svc_reql_remove
1351 */
1352 static void
vscan_svc_reql_remove(vscan_req_t * req)1353 vscan_svc_reql_remove(vscan_req_t *req)
1354 {
1355 ASSERT(MUTEX_HELD(&vscan_svc_mutex));
1356 ASSERT(req->vsr_magic == VS_REQ_MAGIC);
1357
1358 if (vscan_svc_reql_next == req)
1359 vscan_svc_reql_next = list_next(&vscan_svc_reql, req);
1360
1361 list_remove(&vscan_svc_reql, req);
1362 cv_destroy(&(req->vsr_cv));
1363 VN_RELE(req->vsr_vp);
1364
1365 kmem_free(req, sizeof (vscan_req_t));
1366 --(vscan_svc_counts.vsc_reql);
1367 }
1368