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