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
vs_svc_init(uint32_t max_req)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
vs_svc_fini()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
vs_svc_terminate()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
vs_svc_queue_scan_req(vs_scan_req_t * req)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 *
vs_svc_async_scan(void * arg)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
vs_svc_scan_file(vs_svc_node_t * node,vs_scanstamp_t * scanstamp)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
vs_svc_vlog(char * filepath,vs_result_t * result)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
vs_svc_audit(char * filepath,vs_result_t * result)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