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 */
76static void
77auditfilter_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 */
93static void
94auditfilter_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 */
109static void
110auditfilter_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 */
124static void
125auditfilter_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 */
139static int
140auditfilter_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 */
187static int
188auditfilter_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 */
215struct auditfilter_module *
216auditfilter_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 */
291static int
292auditfilterd_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 */
346static void
347auditfilterd_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 */
469int
470auditfilterd_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 */
486void
487auditfilterd_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 */
497void
498audit_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
506void
507audit_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