1 /*-
2  * Copyright (c) 2006 Robert N. M. Watson
3  * All rights reserved.
4  *
5  * This software was developed by Robert Watson for the TrustedBSD Project.
6  *
7  * Redistribution and use in source and binary forms, with or without
8  * modification, are permitted provided that the following conditions
9  * are met:
10  * 1. Redistributions of source code must retain the above copyright
11  *    notice, this list of conditions and the following disclaimer.
12  * 2. Redistributions in binary form must reproduce the above copyright
13  *    notice, this list of conditions and the following disclaimer in the
14  *    documentation and/or other materials provided with the distribution.
15  *
16  * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
17  * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
18  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
19  * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
20  * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
21  * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
22  * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
23  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
24  * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
25  * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
26  * SUCH DAMAGE.
27  *
28  * $P4: //depot/projects/trustedbsd/openbsm/bin/auditfilterd/auditfilterd_conf.c#5 $
29  */
30 
31 /*
32  * Configuration file parser for auditfilterd.  The configuration file is a
33  * very simple format, similar to other BSM configuration files, consisting
34  * of configuration entries of one line each.  The configuration function is
35  * aware of previous runs, and will update the current configuration as
36  * needed.
37  *
38  * Modules are in one of two states: attached, or detached.  If attach fails,
39  * detach is not called because it was not attached.  If a module is attached
40  * and a call to its reinit method fails, we will detach it.
41  *
42  * Modules are passed a (void *) reference to their configuration state so
43  * that they may pass this into any common APIs we provide which may rely on
44  * that state.  Currently, the only such API is the cookie API, which allows
45  * per-instance state to be maintained by a module.  In the future, this will
46  * also be used to support per-instance preselection state.
47  */
48 
49 #include <sys/types.h>
50 
51 #include <config/config.h>
52 #ifdef HAVE_FULL_QUEUE_H
53 #include <sys/queue.h>
54 #else
55 #include <compat/queue.h>
56 #endif
57 
58 #include <bsm/libbsm.h>
59 #include <bsm/audit_filter.h>
60 
61 #include <dlfcn.h>
62 #include <err.h>
63 #include <errno.h>
64 #include <limits.h>
65 #include <stdio.h>
66 #include <stdlib.h>
67 #include <string.h>
68 
69 #include "auditfilterd.h"
70 
71 /*
72  * Free an individual auditfilter_module structure.  Will not shut down the
73  * module, just frees the memory.  Does so conditional on pointers being
74  * non-NULL so that it can be used on partially allocated structures.
75  */
76 static void
77 auditfilter_module_free(struct auditfilter_module *am)
78 {
79 
80 	if (am->am_modulename != NULL)
81 		free(am->am_modulename);
82 	if (am->am_arg_buffer != NULL)
83 		free(am->am_arg_buffer);
84 	if (am->am_argv != NULL)
85 		free(am->am_argv);
86 }
87 
88 /*
89  * Free all memory associated with an auditfilter_module list.  Does not
90  * dlclose() or shut down the modules, just free the memory.  Use
91  * auditfilter_module_list_detach() for that, if required.
92  */
93 static void
94 auditfilter_module_list_free(struct auditfilter_module_list *list)
95 {
96 	struct auditfilter_module *am;
97 
98 	while (!(TAILQ_EMPTY(list))) {
99 		am = TAILQ_FIRST(list);
100 		TAILQ_REMOVE(list, am, am_list);
101 		auditfilter_module_free(am);
102 	}
103 }
104 
105 /*
106  * Detach an attached module from an auditfilter_module structure.  Does not
107  * free the data structure itself.
108  */
109 static void
110 auditfilter_module_detach(struct auditfilter_module *am)
111 {
112 
113 	if (am->am_detach != NULL)
114 		am->am_detach(am);
115 	am->am_cookie = NULL;
116 	(void)dlclose(am->am_dlhandle);
117 	am->am_dlhandle = NULL;
118 }
119 
120 /*
121  * Walk an auditfilter_module list, detaching each module.  Intended to be
122  * combined with auditfilter_module_list_free().
123  */
124 static void
125 auditfilter_module_list_detach(struct auditfilter_module_list *list)
126 {
127 	struct auditfilter_module *am;
128 
129 	TAILQ_FOREACH(am, list, am_list)
130 		auditfilter_module_detach(am);
131 }
132 
133 /*
134  * Given a filled out auditfilter_module, use dlopen() and dlsym() to attach
135  * the module.  If we fail, leave fields in the state we found them.
136  *
137  * XXXRW: Need a better way to report errors.
138  */
139 static int
140 auditfilter_module_attach(struct auditfilter_module *am)
141 {
142 
143 	am->am_dlhandle = dlopen(am->am_modulename, RTLD_NOW);
144 	if (am->am_dlhandle == NULL) {
145 		warnx("auditfilter_module_attach: %s: %s", am->am_modulename,
146 		    dlerror());
147 		return (-1);
148 	}
149 
150 	/*
151 	 * Not implementing these is not considered a failure condition,
152 	 * although we might want to consider warning if obvious stuff is
153 	 * not implemented, such as am_record.
154 	 */
155 	am->am_attach = dlsym(am->am_dlhandle, AUDIT_FILTER_ATTACH_STRING);
156 	am->am_reinit = dlsym(am->am_dlhandle, AUDIT_FILTER_REINIT_STRING);
157 	am->am_record = dlsym(am->am_dlhandle, AUDIT_FILTER_RECORD_STRING);
158 	am->am_rawrecord = dlsym(am->am_dlhandle,
159 	    AUDIT_FILTER_RAWRECORD_STRING);
160 	am->am_detach = dlsym(am->am_dlhandle, AUDIT_FILTER_DETACH_STRING);
161 
162 	if (am->am_attach != NULL) {
163 		if (am->am_attach(am, am->am_argc, am->am_argv)
164 		    != AUDIT_FILTER_SUCCESS) {
165 			warnx("auditfilter_module_attach: %s: failed",
166 			    am->am_modulename);
167 			dlclose(am->am_dlhandle);
168 			am->am_dlhandle = NULL;
169 			am->am_cookie = NULL;
170 			am->am_attach = NULL;
171 			am->am_reinit = NULL;
172 			am->am_record = NULL;
173 			am->am_rawrecord = NULL;
174 			am->am_detach = NULL;
175 			return (-1);
176 		}
177 	}
178 
179 	return (0);
180 }
181 
182 /*
183  * When the arguments for a module are changed, we notify the module through
184  * a call to its reinit method, if any.  Return 0 on success, or -1 on
185  * failure.
186  */
187 static int
188 auditfilter_module_reinit(struct auditfilter_module *am)
189 {
190 
191 	if (am->am_reinit == NULL)
192 		return (0);
193 
194 	if (am->am_reinit(am, am->am_argc, am->am_argv) !=
195 	    AUDIT_FILTER_SUCCESS) {
196 		warnx("auditfilter_module_reinit: %s: failed",
197 		    am->am_modulename);
198 		return (-1);
199 	}
200 
201 	return (0);
202 }
203 
204 /*
205  * Given a configuration line, generate an auditfilter_module structure that
206  * describes it; caller will not pass comments in, so they are not looked
207  * for.  Do not attempt to instantiate it.  Will destroy the contents of
208  * 'buffer'.
209  *
210  * Configuration lines consist of two parts: the module name and arguments
211  * separated by a ':', and then a ','-delimited list of arguments.
212  *
213  * XXXRW: Need to decide where to send the warning output -- stderr for now.
214  */
215 struct auditfilter_module *
216 auditfilter_module_parse(const char *filename, int linenumber, char *buffer)
217 {
218 	char *arguments, *module, **ap;
219 	struct auditfilter_module *am;
220 
221 	am = malloc(sizeof(*am));
222 	if (am == NULL) {
223 		warn("auditfilter_module_parse: %s:%d", filename, linenumber);
224 		return (NULL);
225 	}
226 	bzero(am, sizeof(*am));
227 
228 	/*
229 	 * First, break out the module and arguments strings.  We look for
230 	 * one extra argument to make sure there are no more :'s in the line.
231 	 * That way, we prevent modules from using argument strings that, in
232 	 * the future, may cause problems for adding additional columns.
233 	 */
234 	arguments = buffer;
235 	module = strsep(&arguments, ":");
236 	if (module == NULL || arguments == NULL) {
237 		warnx("auditfilter_module_parse: %s:%d: parse error",
238 		    filename, linenumber);
239 		return (NULL);
240 	}
241 
242 	am->am_modulename = strdup(module);
243 	if (am->am_modulename == NULL) {
244 		warn("auditfilter_module_parse: %s:%d", filename, linenumber);
245 		auditfilter_module_free(am);
246 		return (NULL);
247 	}
248 
249 	am->am_arg_buffer = strdup(buffer);
250 	if (am->am_arg_buffer == NULL) {
251 		warn("auditfilter_module_parse: %s:%d", filename, linenumber);
252 		auditfilter_module_free(am);
253 		return (NULL);
254 	}
255 
256 	/*
257 	 * Now, break out the arguments string into a series of arguments.
258 	 * This is a bit more complicated, and requires cleanup if things go
259 	 * wrong.
260 	 */
261 	am->am_argv = malloc(sizeof(char *) * AUDITFILTERD_CONF_MAXARGS);
262 	if (am->am_argv == NULL) {
263 		warn("auditfilter_module_parse: %s:%d", filename, linenumber);
264 		auditfilter_module_free(am);
265 		return (NULL);
266 	}
267 	bzero(am->am_argv, sizeof(char *) * AUDITFILTERD_CONF_MAXARGS);
268 	am->am_argc = 0;
269 	for (ap = am->am_argv; (*ap = strsep(&arguments, " \t")) != NULL;) {
270 		if (**ap != '\0') {
271 			am->am_argc++;
272 			if (++ap >= &am->am_argv[AUDITFILTERD_CONF_MAXARGS])
273 				break;
274 		}
275 	}
276 	if (ap >= &am->am_argv[AUDITFILTERD_CONF_MAXARGS]) {
277 		warnx("auditfilter_module_parse: %s:%d: too many arguments",
278 		    filename, linenumber);
279 		auditfilter_module_free(am);
280 		return (NULL);
281 	}
282 
283 	return (am);
284 }
285 
286 /*
287  * Read a configuration file, and populate 'list' with the configuration
288  * lines.  Does not attempt to instantiate the configuration, just read it
289  * into a useful set of data structures.
290  */
291 static int
292 auditfilterd_conf_read(const char *filename, FILE *fp,
293     struct auditfilter_module_list *list)
294 {
295 	int error, linenumber, syntaxerror;
296 	struct auditfilter_module *am;
297 	char buffer[LINE_MAX];
298 
299 	syntaxerror = 0;
300 	linenumber = 0;
301 	while (!feof(fp) && !ferror(fp)) {
302 		if (fgets(buffer, LINE_MAX, fp) == NULL)
303 			break;
304 		linenumber++;
305 		if (buffer[0] == '#' || strlen(buffer) < 1)
306 			continue;
307 		buffer[strlen(buffer)-1] = '\0';
308 		am = auditfilter_module_parse(filename, linenumber, buffer);
309 		if (am == NULL) {
310 			syntaxerror = 1;
311 			break;
312 		}
313 		TAILQ_INSERT_HEAD(list, am, am_list);
314 	}
315 
316 	/*
317 	 * File I/O error.
318 	 */
319 	if (ferror(fp)) {
320 		error = errno;
321 		auditfilter_module_list_free(list);
322 		errno = error;
323 		return (-1);
324 	}
325 
326 	/*
327 	 * Syntax error.
328 	 */
329 	if (syntaxerror) {
330 		auditfilter_module_list_free(list);
331 		errno = EINVAL;
332 		return (-1);
333 	}
334 	return (0);
335 }
336 
337 /*
338  * Apply changes necessary to bring a new configuration into force.  The new
339  * configuration data is passed in, and the current configuration is updated
340  * to match it.  The contents of 'list' are freed or otherwise disposed of
341  * before return.
342  *
343  * The algorithms here are not very efficient, but this is an infrequent
344  * operation on very short lists.
345  */
346 static void
347 auditfilterd_conf_apply(struct auditfilter_module_list *list)
348 {
349 	struct auditfilter_module *am1, *am2, *am_tmp;
350 	int argc_tmp, found;
351 	char **argv_tmp;
352 
353 	/*
354 	 * First, remove remove and detach any entries that appear in the
355 	 * current configuration, but not the new configuration.
356 	 */
357 	TAILQ_FOREACH_SAFE(am1, &filter_list, am_list, am_tmp) {
358 		found = 0;
359 		TAILQ_FOREACH(am2, list, am_list) {
360 			if (strcmp(am1->am_modulename, am2->am_modulename)
361 			    == 0) {
362 				found = 1;
363 				break;
364 			}
365 		}
366 		if (found)
367 			continue;
368 
369 		/*
370 		 * am1 appears in filter_list, but not the new list, detach
371 		 * and free the module.
372 		 */
373 		warnx("detaching module %s", am1->am_modulename);
374 		TAILQ_REMOVE(&filter_list, am1, am_list);
375 		auditfilter_module_detach(am1);
376 		auditfilter_module_free(am1);
377 	}
378 
379 	/*
380 	 * Next, update the configuration of any modules that appear in both
381 	 * lists.  We do this by swapping the two argc and argv values and
382 	 * freeing the new one, rather than detaching the old one and
383 	 * attaching the new one.  That way module state is preserved.
384 	 */
385 	TAILQ_FOREACH(am1, &filter_list, am_list) {
386 		found = 0;
387 		TAILQ_FOREACH(am2, list, am_list) {
388 			if (strcmp(am1->am_modulename, am2->am_modulename)
389 			    == 0) {
390 				found = 1;
391 				break;
392 			}
393 		}
394 		if (!found)
395 			continue;
396 
397 		/*
398 		 * Swap the arguments.
399 		 */
400 		argc_tmp = am1->am_argc;
401 		argv_tmp = am1->am_argv;
402 		am1->am_argc = am2->am_argc;
403 		am1->am_argv = am2->am_argv;
404 		am2->am_argc = argc_tmp;
405 		am2->am_argv = argv_tmp;
406 
407 		/*
408 		 * The reinit is a bit tricky: if reinit fails, we actually
409 		 * remove the old entry and detach that, as we don't allow
410 		 * running modules to be out of sync with the configuration
411 		 * file.
412 		 */
413 		warnx("reiniting module %s", am1->am_modulename);
414 		if (auditfilter_module_reinit(am1) != 0) {
415 			warnx("reinit failed for module %s, detaching",
416 			    am1->am_modulename);
417 			TAILQ_REMOVE(&filter_list, am1, am_list);
418 			auditfilter_module_detach(am1);
419 			auditfilter_module_free(am1);
420 		}
421 
422 		/*
423 		 * Free the entry from the new list, which will discard the
424 		 * old arguments.  No need to detach, as it was never
425 		 * attached in the first place.
426 		 */
427 		TAILQ_REMOVE(list, am2, am_list);
428 		auditfilter_module_free(am2);
429 	}
430 
431 	/*
432 	 * Finally, attach any new entries that don't appear in the old
433 	 * configuration, and if they attach successfully, move them to the
434 	 * real configuration list.
435 	 */
436 	TAILQ_FOREACH(am1, list, am_list) {
437 		found = 0;
438 		TAILQ_FOREACH(am2, &filter_list, am_list) {
439 			if (strcmp(am1->am_modulename, am2->am_modulename)
440 			    == 0) {
441 				found = 1;
442 				break;
443 			}
444 		}
445 		if (found)
446 			continue;
447 		/*
448 		 * Attach the entry.  If it succeeds, add to filter_list,
449 		 * otherwise, free.  No need to detach if attach failed.
450 		 */
451 		warnx("attaching module %s", am1->am_modulename);
452 		TAILQ_REMOVE(list, am1, am_list);
453 		if (auditfilter_module_attach(am1) != 0) {
454 			warnx("attaching module %s failed",
455 			    am1->am_modulename);
456 			auditfilter_module_free(am1);
457 		} else
458 			TAILQ_INSERT_HEAD(&filter_list, am1, am_list);
459 	}
460 
461 	if (TAILQ_FIRST(list) != NULL)
462 		warnx("auditfilterd_conf_apply: new list not empty\n");
463 }
464 
465 /*
466  * Read the new configuration file into a local list.  If the configuration
467  * file is parsed OK, then apply the changes.
468  */
469 int
470 auditfilterd_conf(const char *filename, FILE *fp)
471 {
472 	struct auditfilter_module_list list;
473 
474 	TAILQ_INIT(&list);
475 	if (auditfilterd_conf_read(filename, fp, &list) < 0)
476 		return (-1);
477 
478 	auditfilterd_conf_apply(&list);
479 
480 	return (0);
481 }
482 
483 /*
484  * Detach and free all active filter modules for daemon shutdown.
485  */
486 void
487 auditfilterd_conf_shutdown(void)
488 {
489 
490 	auditfilter_module_list_detach(&filter_list);
491 	auditfilter_module_list_free(&filter_list);
492 }
493 
494 /*
495  * APIs to allow modules to query and set their per-instance cookie.
496  */
497 void
498 audit_filter_getcookie(void *instance, void **cookie)
499 {
500 	struct auditfilter_module *am;
501 
502 	am = (struct auditfilter_module *)instance;
503 	*cookie = am->am_cookie;
504 }
505 
506 void
507 audit_filter_setcookie(void *instance, void *cookie)
508 {
509 	struct auditfilter_module *am;
510 
511 	am = (struct auditfilter_module *)instance;
512 	am->am_cookie = cookie;
513 }
514