1212793Sdim/*-
2212793Sdim * Copyright (c) 2006 Robert N. M. Watson
3212793Sdim * All rights reserved.
4212793Sdim *
5212793Sdim * This software was developed by Robert Watson for the TrustedBSD Project.
6212793Sdim *
7212793Sdim * Redistribution and use in source and binary forms, with or without
8212793Sdim * modification, are permitted provided that the following conditions
9212793Sdim * are met:
10212793Sdim * 1. Redistributions of source code must retain the above copyright
11212793Sdim *    notice, this list of conditions and the following disclaimer.
12249423Sdim * 2. Redistributions in binary form must reproduce the above copyright
13249423Sdim *    notice, this list of conditions and the following disclaimer in the
14249423Sdim *    documentation and/or other materials provided with the distribution.
15249423Sdim *
16249423Sdim * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
17212793Sdim * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
18212793Sdim * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
19212793Sdim * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
20249423Sdim * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
21212793Sdim * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
22212793Sdim * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
23212793Sdim * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
24212793Sdim * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
25212793Sdim * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
26212793Sdim * SUCH DAMAGE.
27212793Sdim */
28212793Sdim
29212793Sdim/*
30212793Sdim * Configuration file parser for auditfilterd.  The configuration file is a
31212793Sdim * very simple format, similar to other BSM configuration files, consisting
32212793Sdim * of configuration entries of one line each.  The configuration function is
33212793Sdim * aware of previous runs, and will update the current configuration as
34212793Sdim * needed.
35212793Sdim *
36212793Sdim * Modules are in one of two states: attached, or detached.  If attach fails,
37212793Sdim * detach is not called because it was not attached.  If a module is attached
38212793Sdim * and a call to its reinit method fails, we will detach it.
39212793Sdim *
40212793Sdim * Modules are passed a (void *) reference to their configuration state so
41212793Sdim * that they may pass this into any common APIs we provide which may rely on
42212793Sdim * that state.  Currently, the only such API is the cookie API, which allows
43212793Sdim * per-instance state to be maintained by a module.  In the future, this will
44212793Sdim * also be used to support per-instance preselection state.
45212793Sdim */
46212793Sdim
47212793Sdim#include <sys/types.h>
48212793Sdim
49212793Sdim#include <config/config.h>
50212793Sdim#ifdef HAVE_FULL_QUEUE_H
51212793Sdim#include <sys/queue.h>
52212793Sdim#else
53212793Sdim#include <compat/queue.h>
54212793Sdim#endif
55212793Sdim
56212793Sdim#include <bsm/libbsm.h>
57212793Sdim#include <bsm/audit_filter.h>
58212793Sdim
59212793Sdim#include <dlfcn.h>
60212793Sdim#include <err.h>
61212793Sdim#include <errno.h>
62212793Sdim#include <limits.h>
63212793Sdim#include <stdio.h>
64212793Sdim#include <stdlib.h>
65212793Sdim#include <string.h>
66212793Sdim
67212793Sdim#include "auditfilterd.h"
68212793Sdim
69212793Sdim/*
70212793Sdim * Free an individual auditfilter_module structure.  Will not shut down the
71212793Sdim * module, just frees the memory.  Does so conditional on pointers being
72212793Sdim * non-NULL so that it can be used on partially allocated structures.
73221345Sdim */
74221345Sdimstatic void
75221345Sdimauditfilter_module_free(struct auditfilter_module *am)
76221345Sdim{
77221345Sdim
78221345Sdim	if (am->am_modulename != NULL)
79221345Sdim		free(am->am_modulename);
80221345Sdim	if (am->am_arg_buffer != NULL)
81221345Sdim		free(am->am_arg_buffer);
82221345Sdim	if (am->am_argv != NULL)
83221345Sdim		free(am->am_argv);
84221345Sdim}
85221345Sdim
86221345Sdim/*
87221345Sdim * Free all memory associated with an auditfilter_module list.  Does not
88221345Sdim * dlclose() or shut down the modules, just free the memory.  Use
89221345Sdim * auditfilter_module_list_detach() for that, if required.
90221345Sdim */
91221345Sdimstatic void
92221345Sdimauditfilter_module_list_free(struct auditfilter_module_list *list)
93221345Sdim{
94221345Sdim	struct auditfilter_module *am;
95221345Sdim
96221345Sdim	while (!(TAILQ_EMPTY(list))) {
97221345Sdim		am = TAILQ_FIRST(list);
98221345Sdim		TAILQ_REMOVE(list, am, am_list);
99212793Sdim		auditfilter_module_free(am);
100212793Sdim	}
101212793Sdim}
102212793Sdim
103212793Sdim/*
104212793Sdim * Detach an attached module from an auditfilter_module structure.  Does not
105212793Sdim * free the data structure itself.
106212793Sdim */
107212793Sdimstatic void
108212793Sdimauditfilter_module_detach(struct auditfilter_module *am)
109212793Sdim{
110212793Sdim
111212793Sdim	if (am->am_detach != NULL)
112212793Sdim		am->am_detach(am);
113212793Sdim	am->am_cookie = NULL;
114212793Sdim	(void)dlclose(am->am_dlhandle);
115212793Sdim	am->am_dlhandle = NULL;
116212793Sdim}
117212793Sdim
118212793Sdim/*
119212793Sdim * Walk an auditfilter_module list, detaching each module.  Intended to be
120212793Sdim * combined with auditfilter_module_list_free().
121212793Sdim */
122212793Sdimstatic void
123212793Sdimauditfilter_module_list_detach(struct auditfilter_module_list *list)
124212793Sdim{
125239462Sdim	struct auditfilter_module *am;
126239462Sdim
127212793Sdim	TAILQ_FOREACH(am, list, am_list)
128239462Sdim		auditfilter_module_detach(am);
129212793Sdim}
130212793Sdim
131212793Sdim/*
132212793Sdim * Given a filled out auditfilter_module, use dlopen() and dlsym() to attach
133212793Sdim * the module.  If we fail, leave fields in the state we found them.
134212793Sdim *
135212793Sdim * XXXRW: Need a better way to report errors.
136212793Sdim */
137212793Sdimstatic int
138212793Sdimauditfilter_module_attach(struct auditfilter_module *am)
139212793Sdim{
140212793Sdim
141212793Sdim	am->am_dlhandle = dlopen(am->am_modulename, RTLD_NOW);
142212793Sdim	if (am->am_dlhandle == NULL) {
143212793Sdim		warnx("auditfilter_module_attach: %s: %s", am->am_modulename,
144212793Sdim		    dlerror());
145212793Sdim		return (-1);
146212793Sdim	}
147212793Sdim
148218893Sdim	/*
149218893Sdim	 * Not implementing these is not considered a failure condition,
150218893Sdim	 * although we might want to consider warning if obvious stuff is
151212793Sdim	 * not implemented, such as am_record.
152212793Sdim	 */
153212793Sdim	am->am_attach = dlsym(am->am_dlhandle, AUDIT_FILTER_ATTACH_STRING);
154212793Sdim	am->am_reinit = dlsym(am->am_dlhandle, AUDIT_FILTER_REINIT_STRING);
155212793Sdim	am->am_record = dlsym(am->am_dlhandle, AUDIT_FILTER_RECORD_STRING);
156212793Sdim	am->am_rawrecord = dlsym(am->am_dlhandle,
157218893Sdim	    AUDIT_FILTER_RAWRECORD_STRING);
158218893Sdim	am->am_detach = dlsym(am->am_dlhandle, AUDIT_FILTER_DETACH_STRING);
159218893Sdim
160212793Sdim	if (am->am_attach != NULL) {
161212793Sdim		if (am->am_attach(am, am->am_argc, am->am_argv)
162212793Sdim		    != AUDIT_FILTER_SUCCESS) {
163212793Sdim			warnx("auditfilter_module_attach: %s: failed",
164212793Sdim			    am->am_modulename);
165212793Sdim			dlclose(am->am_dlhandle);
166212793Sdim			am->am_dlhandle = NULL;
167218893Sdim			am->am_cookie = NULL;
168218893Sdim			am->am_attach = NULL;
169218893Sdim			am->am_reinit = NULL;
170212793Sdim			am->am_record = NULL;
171218893Sdim			am->am_rawrecord = NULL;
172212793Sdim			am->am_detach = NULL;
173212793Sdim			return (-1);
174212793Sdim		}
175218893Sdim	}
176212793Sdim
177218893Sdim	return (0);
178218893Sdim}
179218893Sdim
180218893Sdim/*
181218893Sdim * When the arguments for a module are changed, we notify the module through
182218893Sdim * a call to its reinit method, if any.  Return 0 on success, or -1 on
183218893Sdim * failure.
184212793Sdim */
185212793Sdimstatic int
186212793Sdimauditfilter_module_reinit(struct auditfilter_module *am)
187212793Sdim{
188212793Sdim
189212793Sdim	if (am->am_reinit == NULL)
190218893Sdim		return (0);
191218893Sdim
192218893Sdim	if (am->am_reinit(am, am->am_argc, am->am_argv) !=
193212793Sdim	    AUDIT_FILTER_SUCCESS) {
194212793Sdim		warnx("auditfilter_module_reinit: %s: failed",
195212793Sdim		    am->am_modulename);
196212793Sdim		return (-1);
197212793Sdim	}
198212793Sdim
199212793Sdim	return (0);
200212793Sdim}
201218893Sdim
202212793Sdim/*
203212793Sdim * Given a configuration line, generate an auditfilter_module structure that
204212793Sdim * describes it; caller will not pass comments in, so they are not looked
205212793Sdim * for.  Do not attempt to instantiate it.  Will destroy the contents of
206212793Sdim * 'buffer'.
207212793Sdim *
208212793Sdim * Configuration lines consist of two parts: the module name and arguments
209212793Sdim * separated by a ':', and then a ','-delimited list of arguments.
210212793Sdim *
211212793Sdim * XXXRW: Need to decide where to send the warning output -- stderr for now.
212212793Sdim */
213212793Sdimstruct auditfilter_module *
214212793Sdimauditfilter_module_parse(const char *filename, int linenumber, char *buffer)
215212793Sdim{
216212793Sdim	char *arguments, *module, **ap;
217212793Sdim	struct auditfilter_module *am;
218212793Sdim
219	am = malloc(sizeof(*am));
220	if (am == NULL) {
221		warn("auditfilter_module_parse: %s:%d", filename, linenumber);
222		return (NULL);
223	}
224	bzero(am, sizeof(*am));
225
226	/*
227	 * First, break out the module and arguments strings.  We look for
228	 * one extra argument to make sure there are no more :'s in the line.
229	 * That way, we prevent modules from using argument strings that, in
230	 * the future, may cause problems for adding additional columns.
231	 */
232	arguments = buffer;
233	module = strsep(&arguments, ":");
234	if (module == NULL || arguments == NULL) {
235		warnx("auditfilter_module_parse: %s:%d: parse error",
236		    filename, linenumber);
237		return (NULL);
238	}
239
240	am->am_modulename = strdup(module);
241	if (am->am_modulename == NULL) {
242		warn("auditfilter_module_parse: %s:%d", filename, linenumber);
243		auditfilter_module_free(am);
244		return (NULL);
245	}
246
247	am->am_arg_buffer = strdup(buffer);
248	if (am->am_arg_buffer == NULL) {
249		warn("auditfilter_module_parse: %s:%d", filename, linenumber);
250		auditfilter_module_free(am);
251		return (NULL);
252	}
253
254	/*
255	 * Now, break out the arguments string into a series of arguments.
256	 * This is a bit more complicated, and requires cleanup if things go
257	 * wrong.
258	 */
259	am->am_argv = malloc(sizeof(char *) * AUDITFILTERD_CONF_MAXARGS);
260	if (am->am_argv == NULL) {
261		warn("auditfilter_module_parse: %s:%d", filename, linenumber);
262		auditfilter_module_free(am);
263		return (NULL);
264	}
265	bzero(am->am_argv, sizeof(char *) * AUDITFILTERD_CONF_MAXARGS);
266	am->am_argc = 0;
267	for (ap = am->am_argv; (*ap = strsep(&arguments, " \t")) != NULL;) {
268		if (**ap != '\0') {
269			am->am_argc++;
270			if (++ap >= &am->am_argv[AUDITFILTERD_CONF_MAXARGS])
271				break;
272		}
273	}
274	if (ap >= &am->am_argv[AUDITFILTERD_CONF_MAXARGS]) {
275		warnx("auditfilter_module_parse: %s:%d: too many arguments",
276		    filename, linenumber);
277		auditfilter_module_free(am);
278		return (NULL);
279	}
280
281	return (am);
282}
283
284/*
285 * Read a configuration file, and populate 'list' with the configuration
286 * lines.  Does not attempt to instantiate the configuration, just read it
287 * into a useful set of data structures.
288 */
289static int
290auditfilterd_conf_read(const char *filename, FILE *fp,
291    struct auditfilter_module_list *list)
292{
293	int error, linenumber, syntaxerror;
294	struct auditfilter_module *am;
295	char buffer[LINE_MAX];
296
297	syntaxerror = 0;
298	linenumber = 0;
299	while (!feof(fp) && !ferror(fp)) {
300		if (fgets(buffer, LINE_MAX, fp) == NULL)
301			break;
302		linenumber++;
303		if (buffer[0] == '#' || strlen(buffer) < 1)
304			continue;
305		buffer[strlen(buffer)-1] = '\0';
306		am = auditfilter_module_parse(filename, linenumber, buffer);
307		if (am == NULL) {
308			syntaxerror = 1;
309			break;
310		}
311		TAILQ_INSERT_HEAD(list, am, am_list);
312	}
313
314	/*
315	 * File I/O error.
316	 */
317	if (ferror(fp)) {
318		error = errno;
319		auditfilter_module_list_free(list);
320		errno = error;
321		return (-1);
322	}
323
324	/*
325	 * Syntax error.
326	 */
327	if (syntaxerror) {
328		auditfilter_module_list_free(list);
329		errno = EINVAL;
330		return (-1);
331	}
332	return (0);
333}
334
335/*
336 * Apply changes necessary to bring a new configuration into force.  The new
337 * configuration data is passed in, and the current configuration is updated
338 * to match it.  The contents of 'list' are freed or otherwise disposed of
339 * before return.
340 *
341 * The algorithms here are not very efficient, but this is an infrequent
342 * operation on very short lists.
343 */
344static void
345auditfilterd_conf_apply(struct auditfilter_module_list *list)
346{
347	struct auditfilter_module *am1, *am2, *am_tmp;
348	int argc_tmp, found;
349	char **argv_tmp;
350
351	/*
352	 * First, remove remove and detach any entries that appear in the
353	 * current configuration, but not the new configuration.
354	 */
355	TAILQ_FOREACH_SAFE(am1, &filter_list, am_list, am_tmp) {
356		found = 0;
357		TAILQ_FOREACH(am2, list, am_list) {
358			if (strcmp(am1->am_modulename, am2->am_modulename)
359			    == 0) {
360				found = 1;
361				break;
362			}
363		}
364		if (found)
365			continue;
366
367		/*
368		 * am1 appears in filter_list, but not the new list, detach
369		 * and free the module.
370		 */
371		warnx("detaching module %s", am1->am_modulename);
372		TAILQ_REMOVE(&filter_list, am1, am_list);
373		auditfilter_module_detach(am1);
374		auditfilter_module_free(am1);
375	}
376
377	/*
378	 * Next, update the configuration of any modules that appear in both
379	 * lists.  We do this by swapping the two argc and argv values and
380	 * freeing the new one, rather than detaching the old one and
381	 * attaching the new one.  That way module state is preserved.
382	 */
383	TAILQ_FOREACH(am1, &filter_list, am_list) {
384		found = 0;
385		TAILQ_FOREACH(am2, list, am_list) {
386			if (strcmp(am1->am_modulename, am2->am_modulename)
387			    == 0) {
388				found = 1;
389				break;
390			}
391		}
392		if (!found)
393			continue;
394
395		/*
396		 * Swap the arguments.
397		 */
398		argc_tmp = am1->am_argc;
399		argv_tmp = am1->am_argv;
400		am1->am_argc = am2->am_argc;
401		am1->am_argv = am2->am_argv;
402		am2->am_argc = argc_tmp;
403		am2->am_argv = argv_tmp;
404
405		/*
406		 * The reinit is a bit tricky: if reinit fails, we actually
407		 * remove the old entry and detach that, as we don't allow
408		 * running modules to be out of sync with the configuration
409		 * file.
410		 */
411		warnx("reiniting module %s", am1->am_modulename);
412		if (auditfilter_module_reinit(am1) != 0) {
413			warnx("reinit failed for module %s, detaching",
414			    am1->am_modulename);
415			TAILQ_REMOVE(&filter_list, am1, am_list);
416			auditfilter_module_detach(am1);
417			auditfilter_module_free(am1);
418		}
419
420		/*
421		 * Free the entry from the new list, which will discard the
422		 * old arguments.  No need to detach, as it was never
423		 * attached in the first place.
424		 */
425		TAILQ_REMOVE(list, am2, am_list);
426		auditfilter_module_free(am2);
427	}
428
429	/*
430	 * Finally, attach any new entries that don't appear in the old
431	 * configuration, and if they attach successfully, move them to the
432	 * real configuration list.
433	 */
434	TAILQ_FOREACH(am1, list, am_list) {
435		found = 0;
436		TAILQ_FOREACH(am2, &filter_list, am_list) {
437			if (strcmp(am1->am_modulename, am2->am_modulename)
438			    == 0) {
439				found = 1;
440				break;
441			}
442		}
443		if (found)
444			continue;
445		/*
446		 * Attach the entry.  If it succeeds, add to filter_list,
447		 * otherwise, free.  No need to detach if attach failed.
448		 */
449		warnx("attaching module %s", am1->am_modulename);
450		TAILQ_REMOVE(list, am1, am_list);
451		if (auditfilter_module_attach(am1) != 0) {
452			warnx("attaching module %s failed",
453			    am1->am_modulename);
454			auditfilter_module_free(am1);
455		} else
456			TAILQ_INSERT_HEAD(&filter_list, am1, am_list);
457	}
458
459	if (TAILQ_FIRST(list) != NULL)
460		warnx("auditfilterd_conf_apply: new list not empty\n");
461}
462
463/*
464 * Read the new configuration file into a local list.  If the configuration
465 * file is parsed OK, then apply the changes.
466 */
467int
468auditfilterd_conf(const char *filename, FILE *fp)
469{
470	struct auditfilter_module_list list;
471
472	TAILQ_INIT(&list);
473	if (auditfilterd_conf_read(filename, fp, &list) < 0)
474		return (-1);
475
476	auditfilterd_conf_apply(&list);
477
478	return (0);
479}
480
481/*
482 * Detach and free all active filter modules for daemon shutdown.
483 */
484void
485auditfilterd_conf_shutdown(void)
486{
487
488	auditfilter_module_list_detach(&filter_list);
489	auditfilter_module_list_free(&filter_list);
490}
491
492/*
493 * APIs to allow modules to query and set their per-instance cookie.
494 */
495void
496audit_filter_getcookie(void *instance, void **cookie)
497{
498	struct auditfilter_module *am;
499
500	am = (struct auditfilter_module *)instance;
501	*cookie = am->am_cookie;
502}
503
504void
505audit_filter_setcookie(void *instance, void *cookie)
506{
507	struct auditfilter_module *am;
508
509	am = (struct auditfilter_module *)instance;
510	am->am_cookie = cookie;
511}
512