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