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.c#13 $
29 */
30
31/*
32 * Main file for the audit filter daemon, which presents audit records to a
33 * set of run-time registered loadable modules.  This is the main event loop
34 * of the daemon, which handles starting up, waiting for records, and
35 * presenting records to configured modules.  auditfilterd_conf.c handles the
36 * reading and management of the configuration, module list and module state,
37 * etc.
38 */
39
40#include <sys/types.h>
41#include <sys/stat.h>
42#include <sys/time.h>
43
44#include <config/config.h>
45#ifdef HAVE_FULL_QUEUE_H
46#include <sys/queue.h>
47#else
48#include <compat/queue.h>
49#endif
50
51#ifndef HAVE_CLOCK_GETTIME
52#include <compat/clock_gettime.h>
53#endif
54
55#include <bsm/libbsm.h>
56#include <bsm/audit_filter.h>
57#include <bsm/audit_internal.h>
58
59#include <err.h>
60#include <fcntl.h>
61#include <signal.h>
62#include <stdio.h>
63#include <stdlib.h>
64#include <unistd.h>
65
66#include "auditfilterd.h"
67
68/*
69 * Global list of registered filters.
70 */
71struct auditfilter_module_list	filter_list;
72
73/*
74 * Configuration and signal->main flags.
75 */
76int	debug;		/* Debugging mode requested, don't detach. */
77int	reread_config;	/* SIGHUP has been received. */
78int	quit;		/* SIGQUIT/TERM/INT has been received. */
79
80static void
81usage(void)
82{
83
84	fprintf(stderr, "auditfilterd [-d] [-c conffile] [-p pipefile]"
85	    " [-t trailfile]\n");
86	fprintf(stderr, "  -c    Specify configuration file (default: %s)\n",
87	    AUDITFILTERD_CONFFILE);
88	fprintf(stderr, "  -d    Debugging mode, don't daemonize\n");
89	fprintf(stderr, "  -p    Specify pipe file (default: %s)\n",
90	    AUDITFILTERD_PIPEFILE);
91	fprintf(stderr, "  -t    Specify audit trail file (default: none)\n");
92	exit(-1);
93}
94
95static void
96auditfilterd_init(void)
97{
98
99	TAILQ_INIT(&filter_list);
100}
101
102static void
103signal_handler(int signum)
104{
105
106	switch (signum) {
107	case SIGHUP:
108		reread_config++;
109		break;
110
111	case SIGINT:
112	case SIGTERM:
113	case SIGQUIT:
114		quit++;
115		break;
116	}
117}
118
119/*
120 * Present raw BSM to a set of registered and interested filters.
121 */
122static void
123present_rawrecord(struct timespec *ts, u_char *data, u_int len)
124{
125	struct auditfilter_module *am;
126
127	TAILQ_FOREACH(am, &filter_list, am_list) {
128		if (am->am_rawrecord != NULL)
129			(am->am_rawrecord)(am, ts, data, len);
130	}
131}
132
133/*
134 * Parse the BSM into a set of tokens, which will be pased to registered
135 * and interested filters.
136 */
137#define	MAX_TOKENS	128	/* Maximum tokens we handle per record. */
138static void
139present_tokens(struct timespec *ts, u_char *data, u_int len)
140{
141	struct auditfilter_module *am;
142	tokenstr_t tokens[MAX_TOKENS];
143	u_int bytesread;
144	int tokencount;
145
146	tokencount = 0;
147	while (bytesread < len) {
148		if (au_fetch_tok(&tokens[tokencount], data + bytesread,
149		    len - bytesread) == -1)
150			break;
151		bytesread += tokens[tokencount].len;
152		tokencount++;
153	}
154
155	TAILQ_FOREACH(am, &filter_list, am_list) {
156		if (am->am_record != NULL)
157			(am->am_record)(am, ts, tokencount, tokens);
158	}
159}
160
161/*
162 * The main loop spins pulling records out of the record source and passing
163 * them to modules for processing.
164 */
165static void
166mainloop_file(const char *conffile, const char *trailfile, FILE *trail_fp)
167{
168	struct timespec ts;
169	FILE *conf_fp;
170	u_char *buf;
171	int reclen;
172
173	while (1) {
174		/*
175		 * On SIGHUP, we reread the configuration file and reopen
176		 * the trail file.
177		 */
178		if (reread_config) {
179			reread_config = 0;
180			warnx("rereading configuration");
181			conf_fp = fopen(conffile, "r");
182			if (conf_fp == NULL)
183				err(-1, "%s", conffile);
184			auditfilterd_conf(conffile, conf_fp);
185			fclose(conf_fp);
186
187			fclose(trail_fp);
188			trail_fp = fopen(trailfile, "r");
189			if (trail_fp == NULL)
190				err(-1, "%s", trailfile);
191		}
192		if (quit) {
193			warnx("quitting");
194			break;
195		}
196
197		/*
198		 * For now, be relatively unrobust about incomplete records,
199		 * but in the future will want to do better.  Need to look
200		 * more at the right blocking and signal behavior here.
201		 */
202		reclen = au_read_rec(trail_fp, &buf);
203		if (reclen == -1)
204			continue;
205		if (clock_gettime(CLOCK_REALTIME, &ts) < 0)
206			err(-1, "clock_gettime");
207		present_rawrecord(&ts, buf, reclen);
208		present_tokens(&ts, buf, reclen);
209		free(buf);
210	}
211}
212
213/*
214 * The main loop spins pulling records out of the record source and passing
215 * them to modules for processing.  This version of the function accepts
216 * discrete record input from a file descriptor, as opposed to buffered input
217 * from a file stream.
218 */
219static void
220mainloop_pipe(const char *conffile, const char *pipefile __unused, int pipe_fd)
221{
222	u_char record[MAX_AUDIT_RECORD_SIZE];
223	struct timespec ts;
224	FILE *conf_fp;
225	int reclen;
226
227	while (1) {
228		/*
229		 * On SIGHUP, we reread the configuration file.  Unlike with
230		 * a trail file, we don't reopen the pipe, as we don't want
231		 * to miss records which will be flushed if we do.
232		 */
233		if (reread_config) {
234			reread_config = 0;
235			warnx("rereading configuration");
236			conf_fp = fopen(conffile, "r");
237			if (conf_fp == NULL)
238				err(-1, "%s", conffile);
239			auditfilterd_conf(conffile, conf_fp);
240			fclose(conf_fp);
241		}
242		if (quit) {
243			warnx("quitting");
244			break;
245		}
246
247		/*
248		 * For now, be relatively unrobust about incomplete records,
249		 * but in the future will want to do better.  Need to look
250		 * more at the right blocking and signal behavior here.
251		 */
252		reclen = read(pipe_fd, record, MAX_AUDIT_RECORD_SIZE);
253		if (reclen < 0)
254			continue;
255		if (clock_gettime(CLOCK_REALTIME, &ts) < 0)
256			err(-1, "clock_gettime");
257		present_rawrecord(&ts, record, reclen);
258		present_tokens(&ts, record, reclen);
259	}
260}
261
262int
263main(int argc, char *argv[])
264{
265	const char *pipefile, *trailfile, *conffile;
266	FILE *trail_fp, *conf_fp;
267	struct stat sb;
268	int pipe_fd;
269	int ch;
270
271	conffile = AUDITFILTERD_CONFFILE;
272	trailfile = NULL;
273	pipefile = NULL;
274	while ((ch = getopt(argc, argv, "c:dp:t:")) != -1) {
275		switch (ch) {
276		case 'c':
277			conffile = optarg;
278			break;
279
280		case 'd':
281			debug++;
282			break;
283
284		case 't':
285			if (trailfile != NULL || pipefile != NULL)
286				usage();
287			trailfile = optarg;
288			break;
289
290		case 'p':
291			if (pipefile != NULL || trailfile != NULL)
292				usage();
293			pipefile = optarg;
294			break;
295
296		default:
297			usage();
298		}
299	}
300
301	argc -= optind;
302	argv += optind;
303
304	if (argc != 0)
305		usage();
306
307	/*
308	 * We allow only one of a pipe or a trail to be used.  If none is
309	 * specified, we provide a default pipe path.
310	 */
311	if (pipefile == NULL && trailfile == NULL)
312		pipefile = AUDITFILTERD_PIPEFILE;
313
314	if (pipefile != NULL) {
315		pipe_fd = open(pipefile, O_RDONLY);
316		if (pipe_fd < 0)
317			err(-1, "open:%s", pipefile);
318		if (fstat(pipe_fd, &sb) < 0)
319			err(-1, "stat: %s", pipefile);
320		if (!S_ISCHR(sb.st_mode))
321			errx(-1, "fstat: %s not device", pipefile);
322	} else {
323		trail_fp = fopen(trailfile, "r");
324		if (trail_fp == NULL)
325			err(-1, "%s", trailfile);
326	}
327
328	conf_fp = fopen(conffile, "r");
329	if (conf_fp == NULL)
330		err(-1, "%s", conffile);
331
332	auditfilterd_init();
333	if (auditfilterd_conf(conffile, conf_fp) < 0)
334		exit(-1);
335	fclose(conf_fp);
336
337	if (!debug) {
338		if (daemon(0, 0) < 0)
339			err(-1, "daemon");
340	}
341
342	signal(SIGHUP, signal_handler);
343	signal(SIGINT, signal_handler);
344	signal(SIGQUIT, signal_handler);
345	signal(SIGTERM, signal_handler);
346
347	if (pipefile != NULL)
348		mainloop_pipe(conffile, pipefile, pipe_fd);
349	else
350		mainloop_file(conffile, trailfile, trail_fp);
351
352	auditfilterd_conf_shutdown();
353	return (0);
354}
355