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