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  */
71 struct auditfilter_module_list	filter_list;
72 
73 /*
74  * Configuration and signal->main flags.
75  */
76 int	debug;		/* Debugging mode requested, don't detach. */
77 int	reread_config;	/* SIGHUP has been received. */
78 int	quit;		/* SIGQUIT/TERM/INT has been received. */
79 
80 static void
81 usage(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 
95 static void
96 auditfilterd_init(void)
97 {
98 
99 	TAILQ_INIT(&filter_list);
100 }
101 
102 static void
103 signal_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  */
122 static void
123 present_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. */
138 static void
139 present_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  */
165 static void
166 mainloop_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  */
219 static void
220 mainloop_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 
262 int
263 main(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