/*
* CDDL HEADER START
*
* The contents of this file are subject to the terms of the
* Common Development and Distribution License (the "License").
* You may not use this file except in compliance with the License.
*
* You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
* See the License for the specific language governing permissions
* and limitations under the License.
*
* When distributing Covered Code, include this CDDL HEADER in each
* file and include the License file at usr/src/OPENSOLARIS.LICENSE.
* If applicable, add the following below this CDDL HEADER, with the
* fields enclosed by brackets "[]" replaced with your own identifying
* information: Portions Copyright [yyyy] [name of copyright owner]
*
* CDDL HEADER END
*/
/*
* Copyright 2009 Sun Microsystems, Inc. All rights reserved.
* Use is subject to license terms.
*/
#include <sys/sysmacros.h>
/* access derived from scan result (VS_STATUS_XXX) and file attributes */
#define VS_ACCESS_UNDEFINED 0
/*
* vscan_svc_state
*
* +-----------------+
* | VS_SVC_UNCONFIG |
* +-----------------+
* | ^
* | svc_init | svc_fini
* v |
* +-----------------+
* | VS_SVC_IDLE |<----|
* +-----------------+ |
* | |
* | svc_enable |
* |<----------------| |
* v | |
* +-----------------+ | |
* | VS_SVC_ENABLED |--| |
* +-----------------+ |
* | |
* | svc_disable | handler thread exit,
* v | all requests complete
* +-----------------+ |
* | VS_SVC_DISABLED |-----|
* +-----------------+
*
* svc_enable may occur when we are already in the ENABLED
* state if vscand has exited without clean shutdown and
* then reconnected within the delayed disable time period
* (vs_reconnect_timeout) - see vscan_drv
*/
typedef enum {
/*
* vscan_svc_req_state
*
* When a scan request is received from the file system it is
* identified in or inserted into the vscan_svc_reql (INIT).
* If the request is asynchronous 0 is then returned to the caller.
* If the request is synchronous the req's refcnt is incremented
* and the caller waits for the request to complete.
* The refcnt is also incremented when the request is inserted
* in vscan_svc_nodes, and decremented on scan_complete.
*
* vscan_svc_handler processes requests from the request list,
* inserting them into vscan_svc_nodes and the task queue (QUEUED).
* When the task queue call back (vscan_svc_do_scan) is invoked
* the request transitions to IN_PROGRESS state. If the request
* is sucessfully sent to vscand (door_call) and the door response
* is SCANNING then the scan result will be received asynchronously.
* Although unusual, it is possible that the async response is
* received before the door call returns (hence the ASYNC_COMPLETE
* state).
* When the result has been determined / received,
* vscan_svc_scan_complete is invoked to transition the request to
* COMPLETE state, decrement refcnt and signal all waiting callers.
* When the last waiting caller has processed the result (refcnt == 0)
* the request is removed from vscan_svc_reql and vscan_svc_nodes
* and deleted.
*
* | ^
* | reql_insert | refcnt == 0
* v | (delete)
* +------------------------+ +---------------------+
* | VS_SVC_REQ_INIT | -----DISABLE----> | VS_SVC_REQ_COMPLETE |
* +------------------------+ +---------------------+
* | ^
* | insert_req, tq_dispatch |
* v |
* +------------------------+ |
* | VS_SVC_REQ_QUEUED | scan_complete
* +------------------------+ |
* | |
* | tq_callback (do_scan) |
* | |
* v scan not req'd, error, |
* +------------------------+ or door_result != SCANNING |
* | VS_SVC_REQ_IN_PROGRESS |----------------->-------------|
* +------------------------+ |
* | | |
* | | door_result == SCANNING |
* | v |
* | +---------------------------+ async result |
* | | VS_SVC_REQ_SCANNING |-------->---------|
* | +---------------------------+ |
* | |
* | async result |
* v |
* +---------------------------+ door_result = SCANNING |
* | VS_SVC_REQ_ASYNC_COMPLETE |-------->------------------|
* +---------------------------+
*/
typedef enum {
/*
* vscan_svc_reql - the list of pending and in-progress scan requests
*/
typedef struct vscan_req {
} vscan_req_t;
/*
* vscan_svc_nodes - table of files being scanned
*
* The index into this table is passed in the door call to
* vscand. vscand uses the idx to determine which minor node
* to open to read the file data. Within the kernel driver
* the minor device number can thus be used to identify the
* table index to get the appropriate vnode.
*
*/
typedef struct vscan_svc_node {
static int vscan_svc_nodes_sz;
/* vscan_svc_taskq - queue of requests waiting to be sent to vscand */
/* counts of entries in vscan_svc_reql, vscan_svc_nodes & vscan_svc_taskq */
typedef struct {
/*
* vscan_svc_mutex protects the data pertaining to scan requests:
* request list - vscan_svc_reql
* node table - vscan_svc_nodes
*/
/*
* vscan_svc_cfg_mutex protects the configuration data:
* vscan_svc_config, vscan_svc_types
*/
/* configuration data - for virus scan exemption */
/* thread to insert reql entries into vscan_svc_nodes & vscan_svc_taskq */
/* local functions */
static void vscan_svc_taskq_callback(void *);
static int vscan_svc_exempt_filetype(char *);
static int vscan_svc_match_ext(char *, char *, int);
static void vscan_svc_do_scan(vscan_req_t *);
static vs_scan_req_t *vscan_svc_populate_req(int);
static void vscan_svc_process_scan_result(int);
static void vscan_svc_scan_complete(vscan_req_t *);
static void vscan_svc_delete_req(vscan_req_t *);
static int vscan_svc_insert_req(vscan_req_t *);
static void vscan_svc_remove_req(int);
static void vscan_svc_reql_remove(vscan_req_t *);
static int vscan_svc_getattr(int);
static int vscan_svc_setattr(int, int);
/* thread to insert reql entries into vscan_svc_nodes & vscan_svc_taskq */
static void vscan_svc_reql_handler(void);
/*
* vscan_svc_init
*/
int
{
if (vscan_svc_state != VS_SVC_UNCONFIG) {
int, vscan_svc_state);
return (-1);
}
vscan_svc_counts.vsc_reql = 0;
vscan_svc_counts.vsc_node = 0;
vscan_svc_counts.vsc_tq = 0;
return (0);
}
/*
* vscan_svc_fini
*/
void
{
if (vscan_svc_state != VS_SVC_IDLE) {
int, vscan_svc_state);
return;
}
}
/*
* vscan_svc_enable
*/
int
vscan_svc_enable(void)
{
switch (vscan_svc_state) {
case VS_SVC_ENABLED:
/*
* it's possible (and okay) for vscan_svc_enable to be
* called when already enabled if vscand reconnects
* during a delayed disable
*/
break;
case VS_SVC_IDLE:
/* ready to start processing requests */
break;
default:
int, vscan_svc_state);
return (-1);
}
return (0);
}
/*
* vscan_svc_disable
*
* Resources allocated during vscan_svc_enable are free'd by
* the handler thread immediately prior to exiting
*/
void
vscan_svc_disable(void)
{
switch (vscan_svc_state) {
case VS_SVC_ENABLED:
break;
default:
}
}
/*
* vscan_svc_in_use
*/
{
switch (vscan_svc_state) {
case VS_SVC_IDLE:
case VS_SVC_UNCONFIG:
break;
default:
break;
}
return (in_use);
}
/*
* vscan_svc_get_vnode
*
* Get the file vnode indexed by idx.
*/
vnode_t *
{
return (vp);
}
/*
* vscan_svc_scan_file
*
* This function is the entry point for the file system to
* request that a file be virus scanned.
*/
int
{
int access;
return (0);
/* check if size or type exempts file from scanning */
return (0);
return (EACCES);
}
if (vscan_svc_state != VS_SVC_ENABLED) {
int, vscan_svc_state);
return (0);
}
/* insert (or find) request in list */
}
/* asynchronous request: return 0 */
if (async) {
return (0);
}
/* synchronous scan request: wait for result */
++(req->vsr_refcnt);
}
if (time_left == -1) {
}
if (vscan_svc_state == VS_SVC_DISABLED)
else
if ((--req->vsr_refcnt) == 0)
}
/*
* vscan_svc_reql_handler
*
* inserts scan requests (from vscan_svc_reql) into
* vscan_svc_nodes and vscan_svc_taskq
*/
static void
vscan_svc_reql_handler(void)
{
for (;;) {
if ((vscan_svc_state == VS_SVC_DISABLED) &&
(vscan_svc_counts.vsc_reql == 0)) {
/* free resources allocated durining enable */
return;
}
/*
* If disabled, scan_complete any pending requests.
* Otherwise insert pending requests into vscan_svc_nodes
* and vscan_svc_taskq. If no slots are available in
* vscan_svc_nodes break loop and wait for one
*/
if (vscan_svc_state == VS_SVC_DISABLED) {
} else {
/* insert request into vscan_svc_nodes */
break;
/* add the scan request into the taskq */
(void) taskq_dispatch(vscan_svc_taskq,
++(vscan_svc_counts.vsc_tq);
}
}
}
}
static void
{
--(vscan_svc_counts.vsc_tq);
}
/*
* vscan_svc_do_scan
*
* Note: To avoid potential deadlock it is important that
* vscan_svc_mutex is not held during the call to
* vscan_drv_create_note. vscan_drv_create_note enters
* the vscan_drv_mutex and it is possible that a thread
* holding that mutex could be waiting for vscan_svc_mutex.
*/
static void
{
/* if vscan not enabled (shutting down), allow ACCESS */
if (vscan_svc_state != VS_SVC_ENABLED) {
return;
}
if (vscan_svc_getattr(idx) != 0) {
return;
}
/* valid scan_req ptr guaranteed */
/* free up mutex around create node and door call */
else
if (result != VS_STATUS_SCANNING) {
} else { /* async response */
}
}
/*
* vscan_svc_populate_req
*
* Allocate a scan request to be sent to vscand, populating it
* from the data in vscan_svc_nodes[idx].
*
* Returns: scan request object
*/
static vs_scan_req_t *
{
return (scan_req);
}
/*
* vscan_svc_scan_complete
*/
static void
{
if ((--req->vsr_refcnt) == 0)
else
}
/*
* vscan_svc_delete_req
*/
static void
{
int idx;
}
/*
* vscan_svc_scan_result
*
* Invoked from vscan_drv.c on receipt of an ioctl containing
* an async scan result (VS_DRV_IOCTL_RESULT)
* If the vsr_seqnum in the response does not match that in the
* vscan_svc_nodes entry the result is discarded.
*/
void
{
return;
}
return;
}
else
}
/*
* vscan_svc_scan_abort
*
* Abort in-progress scan requests.
*/
void
{
int idx;
continue;
}
}
}
/*
* vscan_svc_process_scan_result
*
* Sets vsn_access and updates file attributes based on vsn_result,
* as follows:
*
* VS_STATUS_INFECTED
* deny access, set quarantine attribute, clear scanstamp
* VS_STATUS_CLEAN
* allow access, set scanstamp,
* if file not modified since scan initiated, clear modified attribute
* VS_STATUS_NO_SCAN
* deny access if file quarantined, otherwise allow access
* VS_STATUS_UNDEFINED, VS_STATUS_ERROR
* deny access if file quarantined, modified or no scanstamp
* otherwise, allow access
*/
static void
{
switch (node->vsn_result) {
case VS_STATUS_INFECTED:
(void) vscan_svc_setattr(idx,
break;
case VS_STATUS_CLEAN:
/* if mtime has changed, don't clear the modified attribute */
node);
break;
}
node->vsn_modified = 0;
(void) vscan_svc_setattr(idx,
break;
case VS_STATUS_NO_SCAN:
if (node->vsn_quarantined)
else
break;
case VS_STATUS_ERROR:
case VS_STATUS_UNDEFINED:
default:
if ((node->vsn_quarantined) ||
(node->vsn_modified) ||
else
break;
}
}
/*
* vscan_svc_getattr
*
* Get the vscan related system attributes, AT_SIZE & AT_MTIME.
*/
static int
{
return (-1);
/* get the attributes */
return (-1);
"file system does not support virus scanning");
return (-1);
}
return (-1);
return (-1);
}
return (0);
}
/*
* vscan_svc_setattr
*
* Set the vscan related system attributes.
*/
static int
{
int len;
return (-1);
/* update the attributes */
return (-1);
if (which & XAT_AV_MODIFIED) {
}
if (which & XAT_AV_QUARANTINED) {
}
if (which & XAT_AV_SCANSTAMP) {
}
/* if access is denied, set mtime to invalidate client cache */
}
return (-1);
return (0);
}
/*
* vscan_svc_configure
*
* store configuration in vscan_svc_config
* set up vscan_svc_types array of pointers into
* vscan_svc_config.vsc_types for efficient searching
*/
int
{
int count = 0;
vscan_svc_config = *conf;
if (count >= VS_TYPES_MAX) {
return (-1);
}
vscan_svc_types[count] = p;
++count;
}
return (0);
}
/*
* vscan_svc_exempt_file
*
* check if a file's size or type exempts it from virus scanning
*
* If the file is exempt from virus scanning, allow will be set
* to define whether files access should be allowed (B_TRUE) or
* denied (B_FALSE)
*
* Returns: 1 exempt
* 0 scan required
*/
static int
{
return (0);
}
DTRACE_PROBE2(vscan__exempt__filesize, char *,
return (1);
}
return (1);
}
return (0);
}
/*
* vscan_svc_exempt_filetype
*
* Each entry in vscan_svc_types includes a rule indicator (+,-)
* followed by the match string for file types to which the rule
* applies. Look for first match of file type in vscan_svc_types
* and return 1 (exempt) if the indicator is '-', and 0 (not exempt)
* if the indicator is '+'.
* If vscan_svc_match_ext fails, or no match is found, return 0
* (not exempt)
*
* Returns 1: exempt, 0: not exempt
*/
static int
{
else
filename++;
ext = "";
else
ext++;
for (i = 0; i < VS_TYPES_MAX; i ++) {
if (vscan_svc_types[i] == 0)
break;
if (rc == -1)
break;
if (rc > 0) {
char *, vscan_svc_types[i]);
break;
}
}
return (exempt);
}
/*
* vscan_svc_match_ext
*
* Performs a case-insensitive match for two strings. The first string
* argument can contain the wildcard characters '?' and '*'
*
* Returns: 0 no match
* 1 match
* -1 recursion error
*/
static int
{
if (depth > VS_EXT_RECURSE_DEPTH)
return (-1);
for (;;) {
switch (*patn) {
case 0:
return (*str == 0);
case '?':
if (*str != 0) {
str++;
patn++;
continue;
}
return (0);
case '*':
patn++;
if (*patn == 0)
return (1);
while (*str) {
return (1);
str++;
}
return (0);
default:
return (0);
}
str++;
patn++;
continue;
}
}
/* NOT REACHED */
}
/*
* vscan_svc_insert_req
*
* Insert request in next available available slot in vscan_svc_nodes
*
* Returns: idx of slot, or -1 if no slot available
*/
static int
{
int idx;
return (-1);
++(vscan_svc_counts.vsc_node);
return (idx);
}
}
return (-1);
}
/*
* vscan_svc_remove_req
*/
static void
{
if (idx != 0) {
sizeof (vscan_svc_node_t));
--(vscan_svc_counts.vsc_node);
}
}
/*
* vscan_svc_reql_find
*/
static vscan_req_t *
{
break;
}
return (req);
}
/*
* vscan_svc_reql_insert
*/
static vscan_req_t *
{
/* if request already in list then return it */
return (req);
/* if list is full return NULL */
return (NULL);
/* create a new request and insert into list */
if (vscan_svc_seqnum == UINT32_MAX)
vscan_svc_seqnum = 0;
if (vscan_svc_reql_next == NULL)
++(vscan_svc_counts.vsc_reql);
/* wake reql handler thread */
return (req);
}
/*
* vscan_svc_reql_remove
*/
static void
{
if (vscan_svc_reql_next == req)
--(vscan_svc_counts.vsc_reql);
}