pflogd.c revision 126355
1/*	$FreeBSD: head/contrib/pf/pflogd/pflogd.c 126355 2004-02-28 17:32:53Z mlaier $	*/
2/*	$OpenBSD: pflogd.c,v 1.21 2003/08/22 21:50:34 david Exp $	*/
3
4/*
5 * Copyright (c) 2001 Theo de Raadt
6 * Copyright (c) 2001 Can Erkin Acar
7 * All rights reserved.
8 *
9 * Redistribution and use in source and binary forms, with or without
10 * modification, are permitted provided that the following conditions
11 * are met:
12 *
13 *    - Redistributions of source code must retain the above copyright
14 *      notice, this list of conditions and the following disclaimer.
15 *    - Redistributions in binary form must reproduce the above
16 *      copyright notice, this list of conditions and the following
17 *      disclaimer in the documentation and/or other materials provided
18 *      with the distribution.
19 *
20 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
21 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
22 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
23 * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
24 * COPYRIGHT HOLDERS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
25 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
26 * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
27 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
28 * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
29 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
30 * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
31 * POSSIBILITY OF SUCH DAMAGE.
32 */
33
34#include <sys/types.h>
35#include <sys/file.h>
36#include <sys/stat.h>
37#include <stdio.h>
38#include <stdlib.h>
39#include <string.h>
40#include <unistd.h>
41#include <pcap-int.h>
42#include <pcap.h>
43#include <syslog.h>
44#include <signal.h>
45#include <errno.h>
46#include <stdarg.h>
47#include <fcntl.h>
48#if defined(__FreeBSD__)
49#include "pidfile.h"
50#else
51#include <util.h>
52#endif
53
54#if defined(__FreeBSD__)
55#define __dead		__volatile
56#endif
57
58#define DEF_SNAPLEN 116		/* default plus allow for larger header of pflog */
59#define PCAP_TO_MS 500		/* pcap read timeout (ms) */
60#define PCAP_NUM_PKTS 1000	/* max number of packets to process at each loop */
61#define PCAP_OPT_FIL 0		/* filter optimization */
62#define FLUSH_DELAY 60		/* flush delay */
63
64#define PFLOGD_LOG_FILE		"/var/log/pflog"
65#define PFLOGD_DEFAULT_IF	"pflog0"
66
67pcap_t *hpcap;
68pcap_dumper_t *dpcap;
69
70int Debug = 0;
71int snaplen = DEF_SNAPLEN;
72
73volatile sig_atomic_t gotsig_close, gotsig_alrm, gotsig_hup;
74
75char *filename = PFLOGD_LOG_FILE;
76char *interface = PFLOGD_DEFAULT_IF;
77char *filter = NULL;
78
79char errbuf[PCAP_ERRBUF_SIZE];
80
81int log_debug = 0;
82unsigned int delay = FLUSH_DELAY;
83
84char *copy_argv(char * const *argv);
85int   init_pcap(void);
86void  logmsg(int priority, const char *message, ...);
87int   reset_dump(void);
88void  sig_alrm(int);
89void  sig_close(int);
90void  sig_hup(int);
91#if defined(__FreeBSD__)
92__volatile void  usage(void);
93#else
94void  usage(void);
95#endif
96
97
98char *
99copy_argv(char * const *argv)
100{
101	size_t len = 0, n;
102	char *buf;
103
104	if (argv == NULL)
105		return (NULL);
106
107	for (n = 0; argv[n]; n++)
108		len += strlen(argv[n])+1;
109	if (len == 0)
110		return (NULL);
111
112	buf = malloc(len);
113	if (buf == NULL)
114		return (NULL);
115
116	strlcpy(buf, argv[0], len);
117	for (n = 1; argv[n]; n++) {
118		strlcat(buf, " ", len);
119		strlcat(buf, argv[n], len);
120	}
121	return (buf);
122}
123
124void
125logmsg(int pri, const char *message, ...)
126{
127	va_list ap;
128	va_start(ap, message);
129
130	if (log_debug) {
131		vfprintf(stderr, message, ap);
132		fprintf(stderr, "\n");
133	} else
134		vsyslog(pri, message, ap);
135	va_end(ap);
136}
137
138__dead void
139usage(void)
140{
141	fprintf(stderr, "usage: pflogd [-D] [-d delay] [-f filename] ");
142	fprintf(stderr, "[-s snaplen] [expression]\n");
143	exit(1);
144}
145
146void
147sig_close(int sig)
148{
149	gotsig_close = 1;
150}
151
152void
153sig_hup(int sig)
154{
155	gotsig_hup = 1;
156}
157
158void
159sig_alrm(int sig)
160{
161	gotsig_alrm = 1;
162}
163
164int
165init_pcap(void)
166{
167	struct bpf_program bprog;
168	pcap_t *oldhpcap = hpcap;
169
170	hpcap = pcap_open_live(interface, snaplen, 1, PCAP_TO_MS, errbuf);
171	if (hpcap == NULL) {
172		logmsg(LOG_ERR, "Failed to initialize: %s", errbuf);
173		hpcap = oldhpcap;
174		return (-1);
175	}
176
177	if (pcap_compile(hpcap, &bprog, filter, PCAP_OPT_FIL, 0) < 0)
178		logmsg(LOG_WARNING, "%s", pcap_geterr(hpcap));
179	else if (pcap_setfilter(hpcap, &bprog) < 0)
180		logmsg(LOG_WARNING, "%s", pcap_geterr(hpcap));
181	if (filter != NULL)
182		free(filter);
183
184	if (pcap_datalink(hpcap) != DLT_PFLOG) {
185		logmsg(LOG_ERR, "Invalid datalink type");
186		pcap_close(hpcap);
187		hpcap = oldhpcap;
188		return (-1);
189	}
190
191	if (oldhpcap)
192		pcap_close(oldhpcap);
193
194	snaplen = pcap_snapshot(hpcap);
195	return (0);
196}
197
198int
199reset_dump(void)
200{
201	struct pcap_file_header hdr;
202	struct stat st;
203	int tmpsnap;
204	FILE *fp;
205
206	if (hpcap == NULL)
207		return (1);
208	if (dpcap) {
209		pcap_dump_close(dpcap);
210		dpcap = 0;
211	}
212
213	/*
214	 * Basically reimplement pcap_dump_open() because it truncates
215	 * files and duplicates headers and such.
216	 */
217	fp = fopen(filename, "a+");
218	if (fp == NULL) {
219		snprintf(hpcap->errbuf, PCAP_ERRBUF_SIZE, "%s: %s",
220		    filename, pcap_strerror(errno));
221		logmsg(LOG_ERR, "Error: %s", pcap_geterr(hpcap));
222		return (1);
223	}
224	if (fstat(fileno(fp), &st) == -1) {
225		snprintf(hpcap->errbuf, PCAP_ERRBUF_SIZE, "%s: %s",
226		    filename, pcap_strerror(errno));
227		logmsg(LOG_ERR, "Error: %s", pcap_geterr(hpcap));
228		return (1);
229	}
230
231	dpcap = (pcap_dumper_t *)fp;
232
233#define TCPDUMP_MAGIC 0xa1b2c3d4
234
235	if (st.st_size == 0) {
236		if (snaplen != pcap_snapshot(hpcap)) {
237			logmsg(LOG_NOTICE, "Using snaplen %d", snaplen);
238			if (init_pcap()) {
239				logmsg(LOG_ERR, "Failed to initialize");
240				if (hpcap == NULL) return (-1);
241				logmsg(LOG_NOTICE, "Using old settings");
242			}
243		}
244		hdr.magic = TCPDUMP_MAGIC;
245		hdr.version_major = PCAP_VERSION_MAJOR;
246		hdr.version_minor = PCAP_VERSION_MINOR;
247		hdr.thiszone = hpcap->tzoff;
248		hdr.snaplen = hpcap->snapshot;
249		hdr.sigfigs = 0;
250		hdr.linktype = hpcap->linktype;
251
252		if (fwrite((char *)&hdr, sizeof(hdr), 1, fp) != 1) {
253			dpcap = NULL;
254			fclose(fp);
255			return (-1);
256		}
257		return (0);
258	}
259
260	/*
261	 * XXX Must read the file, compare the header against our new
262	 * options (in particular, snaplen) and adjust our options so
263	 * that we generate a correct file.
264	 */
265	(void) fseek(fp, 0L, SEEK_SET);
266	if (fread((char *)&hdr, sizeof(hdr), 1, fp) == 1) {
267		if (hdr.magic != TCPDUMP_MAGIC ||
268		    hdr.version_major != PCAP_VERSION_MAJOR ||
269		    hdr.version_minor != PCAP_VERSION_MINOR ||
270		    hdr.linktype != hpcap->linktype) {
271			logmsg(LOG_ERR,
272			    "Invalid/incompatible log file, move it away");
273			fclose(fp);
274			return (1);
275		    }
276		if (hdr.snaplen != snaplen) {
277			logmsg(LOG_WARNING,
278			    "Existing file specifies a snaplen of %u, using it",
279			    hdr.snaplen);
280			tmpsnap = snaplen;
281			snaplen = hdr.snaplen;
282			if (init_pcap()) {
283				logmsg(LOG_ERR, "Failed to re-initialize");
284				if (hpcap == 0)
285					return (-1);
286				logmsg(LOG_NOTICE,
287					"Using old settings, offset: %llu",
288					(unsigned long long)st.st_size);
289			}
290			snaplen = tmpsnap;
291		}
292	}
293
294	(void) fseek(fp, 0L, SEEK_END);
295	return (0);
296}
297
298int
299main(int argc, char **argv)
300{
301	struct pcap_stat pstat;
302	int ch, np;
303
304	while ((ch = getopt(argc, argv, "Dd:s:f:")) != -1) {
305		switch (ch) {
306		case 'D':
307			Debug = 1;
308			break;
309		case 'd':
310			delay = atoi(optarg);
311			if (delay < 5 || delay > 60*60)
312				usage();
313			break;
314		case 'f':
315			filename = optarg;
316			break;
317		case 's':
318			snaplen = atoi(optarg);
319			if (snaplen <= 0)
320				snaplen = DEF_SNAPLEN;
321			break;
322		default:
323			usage();
324		}
325
326	}
327
328	log_debug = Debug;
329	argc -= optind;
330	argv += optind;
331
332	if (!Debug) {
333		openlog("pflogd", LOG_PID | LOG_CONS, LOG_DAEMON);
334		if (daemon(0, 0)) {
335			logmsg(LOG_WARNING, "Failed to become daemon: %s",
336			    strerror(errno));
337		}
338		pidfile(NULL);
339	}
340
341	(void)umask(S_IRWXG | S_IRWXO);
342
343	signal(SIGTERM, sig_close);
344	signal(SIGINT, sig_close);
345	signal(SIGQUIT, sig_close);
346	signal(SIGALRM, sig_alrm);
347	signal(SIGHUP, sig_hup);
348	alarm(delay);
349
350	if (argc) {
351		filter = copy_argv(argv);
352		if (filter == NULL)
353			logmsg(LOG_NOTICE, "Failed to form filter expression");
354	}
355
356	if (init_pcap()) {
357		logmsg(LOG_ERR, "Exiting, init failure");
358		exit(1);
359	}
360
361	if (reset_dump()) {
362		logmsg(LOG_ERR, "Failed to open log file %s", filename);
363		pcap_close(hpcap);
364		exit(1);
365	}
366
367	while (1) {
368		np = pcap_dispatch(hpcap, PCAP_NUM_PKTS, pcap_dump, (u_char *)dpcap);
369		if (np < 0)
370			logmsg(LOG_NOTICE, "%s", pcap_geterr(hpcap));
371
372		if (gotsig_close)
373			break;
374		if (gotsig_hup) {
375			if (reset_dump()) {
376				logmsg(LOG_ERR, "Failed to open log file!");
377				break;
378			}
379			logmsg(LOG_NOTICE, "Reopened logfile");
380			gotsig_hup = 0;
381		}
382
383		if (gotsig_alrm) {
384			/* XXX pcap_dumper is an incomplete type which libpcap
385			 * casts to a FILE* currently.  For now it is safe to
386			 * make the same assumption, however this may change
387			 * in the future.
388			 */
389			if (dpcap) {
390				if (fflush((FILE *)dpcap) == EOF) {
391					break;
392				}
393			}
394			gotsig_alrm = 0;
395			alarm(delay);
396		}
397	}
398
399	logmsg(LOG_NOTICE, "Exiting due to signal");
400	if (dpcap)
401		pcap_dump_close(dpcap);
402
403	if (pcap_stats(hpcap, &pstat) < 0)
404		logmsg(LOG_WARNING, "Reading stats: %s", pcap_geterr(hpcap));
405	else
406		logmsg(LOG_NOTICE, "%u packets received, %u dropped",
407		    pstat.ps_recv, pstat.ps_drop);
408
409	pcap_close(hpcap);
410	if (!Debug)
411		closelog();
412	return (0);
413}
414