xref: /illumos-gate/usr/src/cmd/vscan/vscand/vs_svc.c (revision 20a7641f9918de8574b8b3b47dbe35c4bfc78df1)
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