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 2008 Sun Microsystems, Inc. All rights reserved. 23 * Use is subject to license terms. 24 */ 25 26 /* 27 * Implementation of the "scan file" interface 28 */ 29 30 #include <stdio.h> 31 #include <stdlib.h> 32 #include <unistd.h> 33 #include <string.h> 34 #include <errno.h> 35 #include <syslog.h> 36 #include <sys/types.h> 37 #include <fcntl.h> 38 #include <bsm/adt.h> 39 #include <bsm/adt_event.h> 40 #include <pthread.h> 41 42 #include "vs_incl.h" 43 44 /* 45 * vs_svc_nodes - table of scan requests and their thread id and 46 * scan engine context. 47 * The table is sized by the value passed to vs_svc_init. This 48 * value is obtained from the kernel and represents the maximum 49 * request idx that the kernel will request vscand to process. 50 * The table is indexed by the vsr_idx value passed in 51 * the scan request - always non-zero. This value is also the index 52 * into the kernel scan request table and identifies the instance of 53 * the driver being used to access file data for the scan. Although 54 * this is of no consequence here, it is useful information for debug. 55 * 56 * When a scan request is received a response is sent indicating 57 * one of the following: 58 * VS_STATUS_ERROR - an error occurred 59 * VS_STATUS_NO_SCAN - no scan is required 60 * VS_STATUS_SCANNING - request has been queued for async processing 61 * 62 * If the scan is required (VS_STATUS_SCANNING) a thread is created 63 * to perform the scan. It's tid is saved in vs_svc_nodes. 64 * 65 * In the case of SHUTDOWN, vs_terminate requests that all scan 66 * engine connections be closed, thus termintaing any in-progress 67 * scans, then awaits completion of all scanning threads as identified 68 * in vs_svc_nodes. 69 */ 70 71 typedef struct vs_svc_node { 72 pthread_t vsn_tid; 73 vs_scan_req_t vsn_req; 74 vs_eng_ctx_t vsn_eng; 75 } vs_svc_node_t; 76 77 static vs_svc_node_t *vs_svc_nodes; 78 static uint32_t vs_svc_max_node; /* max idx into vs_svc_nodes */ 79 static pthread_mutex_t vs_svc_mutex = PTHREAD_MUTEX_INITIALIZER; 80 81 82 /* local functions */ 83 static void *vs_svc_async_scan(void *); 84 static int vs_svc_scan_file(vs_svc_node_t *, vs_scanstamp_t *); 85 static void vs_svc_vlog(char *, vs_result_t *); 86 static void vs_svc_audit(char *, vs_result_t *); 87 88 89 /* 90 * vs_svc_init, vs_svc_fini 91 * 92 * Invoked on daemon load and unload 93 */ 94 int 95 vs_svc_init(uint32_t max_req) 96 { 97 vs_svc_max_node = max_req; 98 vs_svc_nodes = (vs_svc_node_t *) 99 calloc(max_req + 1, sizeof (vs_svc_node_t)); 100 101 return (vs_svc_nodes == NULL ? -1 : 0); 102 } 103 104 void 105 vs_svc_fini() 106 { 107 if (vs_svc_nodes) 108 free(vs_svc_nodes); 109 } 110 111 112 /* 113 * vs_svc_terminate 114 * 115 * Close all scan engine connections to terminate in-progress scan 116 * requests, and wait for all threads in vs_svc_nodes to complete 117 */ 118 void 119 vs_svc_terminate() 120 { 121 int i; 122 pthread_t tid; 123 124 /* close connections to abort requests */ 125 vs_eng_close_connections(); 126 127 /* wait for threads */ 128 for (i = 1; i <= vs_svc_max_node; i++) { 129 130 (void) pthread_mutex_lock(&vs_svc_mutex); 131 tid = vs_svc_nodes[i].vsn_tid; 132 (void) pthread_mutex_unlock(&vs_svc_mutex); 133 134 if (tid != 0) 135 (void) pthread_join(tid, NULL); 136 } 137 } 138 139 140 /* 141 * vs_svc_queue_scan_req 142 * 143 * Determine if the file needs to be scanned - either it has 144 * been modified or its scanstamp is not current. 145 * Initiate a thread to process the request, saving the tid 146 * in vs_svc_nodes[idx].vsn_tid, where idx is the vsr_idx passed in 147 * the scan request. 148 * 149 * Returns: VS_STATUS_ERROR - error 150 * VS_STATUS_NO_SCAN - no scan required 151 * VS_STATUS_SCANNING - async scan initiated 152 */ 153 int 154 vs_svc_queue_scan_req(vs_scan_req_t *req) 155 { 156 pthread_t tid; 157 vs_svc_node_t *node; 158 159 /* No scan if file quarantined */ 160 if (req->vsr_quarantined) 161 return (VS_STATUS_NO_SCAN); 162 163 /* No scan if file not modified AND scanstamp is current */ 164 if ((req->vsr_modified == 0) && 165 vs_eng_scanstamp_current(req->vsr_scanstamp)) { 166 return (VS_STATUS_NO_SCAN); 167 } 168 169 /* scan required */ 170 node = &(vs_svc_nodes[req->vsr_idx]); 171 172 (void) pthread_mutex_lock(&vs_svc_mutex); 173 if ((node->vsn_tid != 0) || (req->vsr_idx > vs_svc_max_node)) { 174 (void) pthread_mutex_unlock(&vs_svc_mutex); 175 return (VS_STATUS_ERROR); 176 } 177 178 node->vsn_req = *req; 179 180 if (pthread_create(&tid, NULL, vs_svc_async_scan, (void *)node) != 0) { 181 (void) pthread_mutex_unlock(&vs_svc_mutex); 182 return (VS_STATUS_ERROR); 183 } 184 185 node->vsn_tid = tid; 186 (void) pthread_mutex_unlock(&vs_svc_mutex); 187 188 return (VS_STATUS_SCANNING); 189 } 190 191 192 /* 193 * vs_svc_async_scan 194 * 195 * Initialize response structure, invoke vs_svc_scan_file to 196 * perform the scan, then send the result to the kernel. 197 */ 198 static void * 199 vs_svc_async_scan(void *arg) 200 { 201 vs_svc_node_t *node = (vs_svc_node_t *)arg; 202 vs_scan_req_t *scan_req = &(node->vsn_req); 203 vs_scan_rsp_t scan_rsp; 204 205 scan_rsp.vsr_idx = scan_req->vsr_idx; 206 scan_rsp.vsr_seqnum = scan_req->vsr_seqnum; 207 scan_rsp.vsr_result = vs_svc_scan_file(node, &scan_rsp.vsr_scanstamp); 208 209 /* clear node and send async response to kernel */ 210 (void) pthread_mutex_lock(&vs_svc_mutex); 211 (void) memset(node, 0, sizeof (vs_svc_node_t)); 212 (void) pthread_mutex_unlock(&vs_svc_mutex); 213 214 (void) vscand_kernel_result(&scan_rsp); 215 216 return (NULL); 217 } 218 219 220 /* 221 * vs_svc_scan_file 222 * 223 * vs_svc_scan_file is responsible for: 224 * - obtaining & releasing a scan engine connection 225 * - invoking the scan engine interface code to do the scan 226 * - retrying a failed scan (up to VS_MAX_RETRY times) 227 * - updating scan statistics 228 * - logging virus information 229 * 230 * 231 * Returns: 232 * VS_STATUS_NO_SCAN - scan not reqd; daemon shutting down 233 * VS_STATUS_CLEAN - scan success. File clean. 234 * new scanstamp returned in scanstamp param. 235 * VS_STATUS_INFECTED - scan success. File infected. 236 * VS_STATUS_ERROR - scan failure either in vscand or scan engine. 237 */ 238 static int 239 vs_svc_scan_file(vs_svc_node_t *node, vs_scanstamp_t *scanstamp) 240 { 241 char devname[MAXPATHLEN]; 242 int flags = 0; 243 int retries; 244 vs_result_t result; 245 vs_scan_req_t *req = &(node->vsn_req); 246 vs_eng_ctx_t *eng = &(node->vsn_eng); 247 248 (void) snprintf(devname, MAXPATHLEN, "%s%d", VS_DRV_PATH, req->vsr_idx); 249 250 /* initialize response scanstamp to current scanstamp value */ 251 (void) strlcpy(*scanstamp, req->vsr_scanstamp, sizeof (vs_scanstamp_t)); 252 253 (void) memset(&result, 0, sizeof (vs_result_t)); 254 result.vsr_rc = VS_RESULT_UNDEFINED; 255 256 for (retries = 0; retries <= VS_MAX_RETRY; retries++) { 257 /* get engine connection */ 258 if (vs_eng_get(eng, (retries != 0)) != 0) { 259 result.vsr_rc = VS_RESULT_ERROR; 260 continue; 261 } 262 263 /* shutdown could occur while waiting for engine connection */ 264 if (vscand_get_state() == VS_STATE_SHUTDOWN) { 265 vs_eng_release(eng); 266 return (VS_STATUS_NO_SCAN); 267 } 268 269 /* scan file */ 270 (void) vs_icap_scan_file(eng, devname, req->vsr_path, 271 req->vsr_size, flags, &result); 272 273 /* if no error, clear error state on engine and break */ 274 if ((result.vsr_rc != VS_RESULT_SE_ERROR) && 275 (result.vsr_rc != VS_RESULT_ERROR)) { 276 vs_eng_set_error(eng, 0); 277 vs_eng_release(eng); 278 break; 279 } 280 281 /* treat error on shutdown as scan not required */ 282 if (vscand_get_state() == VS_STATE_SHUTDOWN) { 283 vs_eng_release(eng); 284 return (VS_STATUS_NO_SCAN); 285 } 286 287 /* set engine's error state and update engine stats */ 288 if (result.vsr_rc == VS_RESULT_SE_ERROR) 289 vs_eng_set_error(eng, 1); 290 291 vs_eng_release(eng); 292 } 293 294 vs_stats_set(result.vsr_rc); 295 296 /* 297 * VS_RESULT_CLEANED - file infected, cleaned data available 298 * VS_RESULT_FORBIDDEN - file infected, no cleaned data 299 * Log virus, write audit record and return INFECTED status 300 */ 301 if (result.vsr_rc == VS_RESULT_CLEANED || 302 result.vsr_rc == VS_RESULT_FORBIDDEN) { 303 vs_svc_vlog(req->vsr_path, &result); 304 vs_svc_audit(req->vsr_path, &result); 305 return (VS_STATUS_INFECTED); 306 } 307 308 /* VS_RESULT_CLEAN - Set the scanstamp and return CLEAN status */ 309 if (result.vsr_rc == VS_RESULT_CLEAN) { 310 (void) strlcpy(*scanstamp, result.vsr_scanstamp, 311 sizeof (vs_scanstamp_t)); 312 return (VS_STATUS_CLEAN); 313 } 314 315 return (VS_STATUS_ERROR); 316 } 317 318 319 /* 320 * vs_svc_vlog 321 * 322 * log details of infections detected in syslig 323 * If virus log is configured log details there too 324 */ 325 static void 326 vs_svc_vlog(char *filepath, vs_result_t *result) 327 { 328 FILE *fp = NULL; 329 time_t sec; 330 struct tm *timestamp; 331 char timebuf[18]; /* MM/DD/YY hh:mm:ss */ 332 int i; 333 char *log; 334 335 /* syslog */ 336 if (result->vsr_nviolations == 0) { 337 syslog(LOG_NOTICE, "quarantine %s\n", filepath); 338 } else { 339 for (i = 0; i < result->vsr_nviolations; i++) { 340 syslog(LOG_NOTICE, "quarantine %s %d - %s\n", 341 filepath, 342 result->vsr_vrec[i].vr_id, 343 result->vsr_vrec[i].vr_desc); 344 } 345 } 346 347 /* log file */ 348 if (((log = vscand_viruslog()) == NULL) || 349 ((fp = fopen(log, "a")) == NULL)) { 350 return; 351 } 352 353 (void) time(&sec); 354 timestamp = localtime(&sec); 355 (void) strftime(timebuf, sizeof (timebuf), "%D %T", timestamp); 356 357 if (result->vsr_nviolations == 0) { 358 (void) fprintf(fp, "%s quarantine %d[%s]\n", 359 timebuf, strlen(filepath), filepath); 360 } else { 361 for (i = 0; i < result->vsr_nviolations; i++) { 362 (void) fprintf(fp, "%s quarantine %d[%s] %d - %d[%s]\n", 363 timebuf, strlen(filepath), filepath, 364 result->vsr_vrec[i].vr_id, 365 strlen(result->vsr_vrec[i].vr_desc), 366 result->vsr_vrec[i].vr_desc); 367 } 368 } 369 370 (void) fclose(fp); 371 } 372 373 374 /* 375 * vs_svc_audit 376 * 377 * Generate AUE_vscan_quarantine audit record containing name 378 * of infected file, and violation details if available. 379 */ 380 static void 381 vs_svc_audit(char *filepath, vs_result_t *result) 382 { 383 int i; 384 char *violations[VS_MAX_VIOLATIONS]; 385 char data[VS_MAX_VIOLATIONS][VS_DESCRIPTION_MAX]; 386 adt_session_data_t *ah; 387 adt_termid_t *p_tid; 388 adt_event_data_t *event; 389 390 if (adt_start_session(&ah, NULL, ADT_USE_PROC_DATA)) { 391 syslog(LOG_AUTH | LOG_ALERT, "adt_start_session: %m"); 392 return; 393 } 394 395 if (adt_load_ttyname("/dev/console", &p_tid) != 0) { 396 syslog(LOG_AUTH | LOG_ALERT, 397 "adt_load_ttyname(/dev/console): %m"); 398 return; 399 } 400 401 if (adt_set_user(ah, ADT_NO_ATTRIB, ADT_NO_ATTRIB, ADT_NO_ATTRIB, 402 ADT_NO_ATTRIB, p_tid, ADT_NEW) != 0) { 403 syslog(LOG_AUTH | LOG_ALERT, "adt_set_user(ADT_NO_ATTRIB): %m"); 404 (void) adt_end_session(ah); 405 return; 406 } 407 408 if ((event = adt_alloc_event(ah, ADT_vscan_quarantine)) == NULL) { 409 syslog(LOG_AUTH | LOG_ALERT, 410 "adt_alloc_event(ADT_vscan_quarantine)): %m"); 411 (void) adt_end_session(ah); 412 return; 413 } 414 415 /* populate vscan audit event */ 416 event->adt_vscan_quarantine.file = filepath; 417 for (i = 0; i < result->vsr_nviolations; i++) { 418 (void) snprintf(data[i], VS_DESCRIPTION_MAX, "%d - %s", 419 result->vsr_vrec[i].vr_id, result->vsr_vrec[i].vr_desc); 420 violations[i] = data[i]; 421 } 422 423 event->adt_vscan_quarantine.violations = (char **)violations; 424 event->adt_vscan_quarantine.nviolations = result->vsr_nviolations; 425 426 if (adt_put_event(event, ADT_SUCCESS, ADT_SUCCESS)) 427 syslog(LOG_AUTH | LOG_ALERT, "adt_put_event: %m"); 428 429 adt_free_event(event); 430 (void) adt_end_session(ah); 431 } 432