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