pflogd.c revision 171172
1/*	$OpenBSD: pflogd.c,v 1.37 2006/10/26 13:34:47 jmc 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/cdefs.h>
34__FBSDID("$FreeBSD: head/contrib/pf/pflogd/pflogd.c 171172 2007-07-03 12:30:03Z mlaier $");
35
36#include <sys/types.h>
37#include <sys/ioctl.h>
38#include <sys/file.h>
39#include <sys/stat.h>
40#include <stdio.h>
41#include <stdlib.h>
42#include <string.h>
43#include <unistd.h>
44#include <pcap-int.h>
45#include <pcap.h>
46#include <syslog.h>
47#include <signal.h>
48#include <errno.h>
49#include <stdarg.h>
50#include <fcntl.h>
51#ifdef __FreeBSD__
52#include "pidfile.h"
53#else
54#include <util.h>
55#endif
56
57#include "pflogd.h"
58
59pcap_t *hpcap;
60static FILE *dpcap;
61
62int Debug = 0;
63static int snaplen = DEF_SNAPLEN;
64static int cur_snaplen = DEF_SNAPLEN;
65
66volatile sig_atomic_t gotsig_close, gotsig_alrm, gotsig_hup;
67
68char *filename = PFLOGD_LOG_FILE;
69char *interface = PFLOGD_DEFAULT_IF;
70char *filter = NULL;
71
72char errbuf[PCAP_ERRBUF_SIZE];
73
74int log_debug = 0;
75unsigned int delay = FLUSH_DELAY;
76
77char *copy_argv(char * const *);
78void  dump_packet(u_char *, const struct pcap_pkthdr *, const u_char *);
79void  dump_packet_nobuf(u_char *, const struct pcap_pkthdr *, const u_char *);
80int   flush_buffer(FILE *);
81int   init_pcap(void);
82void  logmsg(int, const char *, ...);
83void  purge_buffer(void);
84int   reset_dump(int);
85int   scan_dump(FILE *, off_t);
86int   set_snaplen(int);
87void  set_suspended(int);
88void  sig_alrm(int);
89void  sig_close(int);
90void  sig_hup(int);
91void  usage(void);
92
93static int try_reset_dump(int);
94
95/* buffer must always be greater than snaplen */
96static int    bufpkt = 0;	/* number of packets in buffer */
97static int    buflen = 0;	/* allocated size of buffer */
98static char  *buffer = NULL;	/* packet buffer */
99static char  *bufpos = NULL;	/* position in buffer */
100static int    bufleft = 0;	/* bytes left in buffer */
101
102/* if error, stop logging but count dropped packets */
103static int suspended = -1;
104static long packets_dropped = 0;
105
106void
107set_suspended(int s)
108{
109	if (suspended == s)
110		return;
111
112	suspended = s;
113	setproctitle("[%s] -s %d -i %s -f %s",
114	    suspended ? "suspended" : "running",
115	    cur_snaplen, interface, filename);
116}
117
118char *
119copy_argv(char * const *argv)
120{
121	size_t len = 0, n;
122	char *buf;
123
124	if (argv == NULL)
125		return (NULL);
126
127	for (n = 0; argv[n]; n++)
128		len += strlen(argv[n])+1;
129	if (len == 0)
130		return (NULL);
131
132	buf = malloc(len);
133	if (buf == NULL)
134		return (NULL);
135
136	strlcpy(buf, argv[0], len);
137	for (n = 1; argv[n]; n++) {
138		strlcat(buf, " ", len);
139		strlcat(buf, argv[n], len);
140	}
141	return (buf);
142}
143
144void
145logmsg(int pri, const char *message, ...)
146{
147	va_list ap;
148	va_start(ap, message);
149
150	if (log_debug) {
151		vfprintf(stderr, message, ap);
152		fprintf(stderr, "\n");
153	} else
154		vsyslog(pri, message, ap);
155	va_end(ap);
156}
157
158#ifdef __FreeBSD__
159__dead2 void
160#else
161__dead void
162#endif
163usage(void)
164{
165	fprintf(stderr, "usage: pflogd [-Dx] [-d delay] [-f filename]");
166	fprintf(stderr, " [-i interface] [-s snaplen]\n");
167	fprintf(stderr, "              [expression]\n");
168	exit(1);
169}
170
171void
172sig_close(int sig)
173{
174	gotsig_close = 1;
175}
176
177void
178sig_hup(int sig)
179{
180	gotsig_hup = 1;
181}
182
183void
184sig_alrm(int sig)
185{
186	gotsig_alrm = 1;
187}
188
189void
190set_pcap_filter(void)
191{
192	struct bpf_program bprog;
193
194	if (pcap_compile(hpcap, &bprog, filter, PCAP_OPT_FIL, 0) < 0)
195		logmsg(LOG_WARNING, "%s", pcap_geterr(hpcap));
196	else {
197		if (pcap_setfilter(hpcap, &bprog) < 0)
198			logmsg(LOG_WARNING, "%s", pcap_geterr(hpcap));
199		pcap_freecode(&bprog);
200	}
201}
202
203int
204init_pcap(void)
205{
206	hpcap = pcap_open_live(interface, snaplen, 1, PCAP_TO_MS, errbuf);
207	if (hpcap == NULL) {
208		logmsg(LOG_ERR, "Failed to initialize: %s", errbuf);
209		return (-1);
210	}
211
212	if (pcap_datalink(hpcap) != DLT_PFLOG) {
213		logmsg(LOG_ERR, "Invalid datalink type");
214		pcap_close(hpcap);
215		hpcap = NULL;
216		return (-1);
217	}
218
219	set_pcap_filter();
220
221	cur_snaplen = snaplen = pcap_snapshot(hpcap);
222
223	/* lock */
224	if (ioctl(pcap_fileno(hpcap), BIOCLOCK) < 0) {
225		logmsg(LOG_ERR, "BIOCLOCK: %s", strerror(errno));
226		return (-1);
227	}
228
229	return (0);
230}
231
232int
233set_snaplen(int snap)
234{
235	if (priv_set_snaplen(snap))
236		return (1);
237
238	if (cur_snaplen > snap)
239		purge_buffer();
240
241	cur_snaplen = snap;
242
243	return (0);
244}
245
246int
247reset_dump(int nomove)
248{
249	int ret;
250
251	for (;;) {
252		ret = try_reset_dump(nomove);
253		if (ret <= 0)
254			break;
255	}
256
257	return (ret);
258}
259
260/*
261 * tries to (re)open log file, nomove flag is used with -x switch
262 * returns 0: success, 1: retry (log moved), -1: error
263 */
264int
265try_reset_dump(int nomove)
266{
267	struct pcap_file_header hdr;
268	struct stat st;
269	int fd;
270	FILE *fp;
271
272	if (hpcap == NULL)
273		return (-1);
274
275	if (dpcap) {
276		flush_buffer(dpcap);
277		fclose(dpcap);
278		dpcap = NULL;
279	}
280
281	/*
282	 * Basically reimplement pcap_dump_open() because it truncates
283	 * files and duplicates headers and such.
284	 */
285	fd = priv_open_log();
286	if (fd < 0)
287		return (-1);
288
289	fp = fdopen(fd, "a+");
290
291	if (fp == NULL) {
292		logmsg(LOG_ERR, "Error: %s: %s", filename, strerror(errno));
293		close(fd);
294		return (-1);
295	}
296	if (fstat(fileno(fp), &st) == -1) {
297		logmsg(LOG_ERR, "Error: %s: %s", filename, strerror(errno));
298		fclose(fp);
299		return (-1);
300	}
301
302	/* set FILE unbuffered, we do our own buffering */
303	if (setvbuf(fp, NULL, _IONBF, 0)) {
304		logmsg(LOG_ERR, "Failed to set output buffers");
305		fclose(fp);
306		return (-1);
307	}
308
309#define TCPDUMP_MAGIC 0xa1b2c3d4
310
311	if (st.st_size == 0) {
312		if (snaplen != cur_snaplen) {
313			logmsg(LOG_NOTICE, "Using snaplen %d", snaplen);
314			if (set_snaplen(snaplen))
315				logmsg(LOG_WARNING,
316				    "Failed, using old settings");
317		}
318		hdr.magic = TCPDUMP_MAGIC;
319		hdr.version_major = PCAP_VERSION_MAJOR;
320		hdr.version_minor = PCAP_VERSION_MINOR;
321		hdr.thiszone = hpcap->tzoff;
322		hdr.snaplen = hpcap->snapshot;
323		hdr.sigfigs = 0;
324		hdr.linktype = hpcap->linktype;
325
326		if (fwrite((char *)&hdr, sizeof(hdr), 1, fp) != 1) {
327			fclose(fp);
328			return (-1);
329		}
330	} else if (scan_dump(fp, st.st_size)) {
331		fclose(fp);
332		if (nomove || priv_move_log()) {
333			logmsg(LOG_ERR,
334			    "Invalid/incompatible log file, move it away");
335			return (-1);
336		}
337		return (1);
338	}
339
340	dpcap = fp;
341
342	set_suspended(0);
343	flush_buffer(fp);
344
345	return (0);
346}
347
348int
349scan_dump(FILE *fp, off_t size)
350{
351	struct pcap_file_header hdr;
352#ifdef __FreeBSD__
353	struct pcap_sf_pkthdr ph;
354#else
355	struct pcap_pkthdr ph;
356#endif
357	off_t pos;
358
359	/*
360	 * Must read the file, compare the header against our new
361	 * options (in particular, snaplen) and adjust our options so
362	 * that we generate a correct file. Furthermore, check the file
363	 * for consistency so that we can append safely.
364	 *
365	 * XXX this may take a long time for large logs.
366	 */
367	(void) fseek(fp, 0L, SEEK_SET);
368
369	if (fread((char *)&hdr, sizeof(hdr), 1, fp) != 1) {
370		logmsg(LOG_ERR, "Short file header");
371		return (1);
372	}
373
374	if (hdr.magic != TCPDUMP_MAGIC ||
375	    hdr.version_major != PCAP_VERSION_MAJOR ||
376	    hdr.version_minor != PCAP_VERSION_MINOR ||
377	    hdr.linktype != hpcap->linktype ||
378	    hdr.snaplen > PFLOGD_MAXSNAPLEN) {
379		return (1);
380	}
381
382	pos = sizeof(hdr);
383
384	while (!feof(fp)) {
385		off_t len = fread((char *)&ph, 1, sizeof(ph), fp);
386		if (len == 0)
387			break;
388
389		if (len != sizeof(ph))
390			goto error;
391		if (ph.caplen > hdr.snaplen || ph.caplen > PFLOGD_MAXSNAPLEN)
392			goto error;
393		pos += sizeof(ph) + ph.caplen;
394		if (pos > size)
395			goto error;
396		fseek(fp, ph.caplen, SEEK_CUR);
397	}
398
399	if (pos != size)
400		goto error;
401
402	if (hdr.snaplen != cur_snaplen) {
403		logmsg(LOG_WARNING,
404		       "Existing file has different snaplen %u, using it",
405		       hdr.snaplen);
406		if (set_snaplen(hdr.snaplen)) {
407			logmsg(LOG_WARNING,
408			       "Failed, using old settings, offset %llu",
409			       (unsigned long long) size);
410		}
411	}
412
413	return (0);
414
415 error:
416	logmsg(LOG_ERR, "Corrupted log file.");
417	return (1);
418}
419
420/* dump a packet directly to the stream, which is unbuffered */
421void
422dump_packet_nobuf(u_char *user, const struct pcap_pkthdr *h, const u_char *sp)
423{
424	FILE *f = (FILE *)user;
425#ifdef __FreeBSD__
426	struct pcap_sf_pkthdr sh;
427#endif
428
429	if (suspended) {
430		packets_dropped++;
431		return;
432	}
433
434#ifdef __FreeBSD__
435	sh.ts.tv_sec = (bpf_int32)h->ts.tv_sec;
436	sh.ts.tv_usec = (bpf_int32)h->ts.tv_usec;
437	sh.caplen = h->caplen;
438	sh.len = h->len;
439
440	if (fwrite((char *)&sh, sizeof(sh), 1, f) != 1) {
441#else
442	if (fwrite((char *)h, sizeof(*h), 1, f) != 1) {
443#endif
444		off_t pos = ftello(f);
445
446		/* try to undo header to prevent corruption */
447#ifdef __FreeBSD__
448		if (pos < sizeof(sh) ||
449		    ftruncate(fileno(f), pos - sizeof(sh))) {
450#else
451		if (pos < sizeof(*h) ||
452		    ftruncate(fileno(f), pos - sizeof(*h))) {
453#endif
454			logmsg(LOG_ERR, "Write failed, corrupted logfile!");
455			set_suspended(1);
456			gotsig_close = 1;
457			return;
458		}
459		goto error;
460	}
461
462	if (fwrite((char *)sp, h->caplen, 1, f) != 1)
463		goto error;
464
465	return;
466
467error:
468	set_suspended(1);
469	packets_dropped ++;
470	logmsg(LOG_ERR, "Logging suspended: fwrite: %s", strerror(errno));
471}
472
473int
474flush_buffer(FILE *f)
475{
476	off_t offset;
477	int len = bufpos - buffer;
478
479	if (len <= 0)
480		return (0);
481
482	offset = ftello(f);
483	if (offset == (off_t)-1) {
484		set_suspended(1);
485		logmsg(LOG_ERR, "Logging suspended: ftello: %s",
486		    strerror(errno));
487		return (1);
488	}
489
490	if (fwrite(buffer, len, 1, f) != 1) {
491		set_suspended(1);
492		logmsg(LOG_ERR, "Logging suspended: fwrite: %s",
493		    strerror(errno));
494		ftruncate(fileno(f), offset);
495		return (1);
496	}
497
498	set_suspended(0);
499	bufpos = buffer;
500	bufleft = buflen;
501	bufpkt = 0;
502
503	return (0);
504}
505
506void
507purge_buffer(void)
508{
509	packets_dropped += bufpkt;
510
511	set_suspended(0);
512	bufpos = buffer;
513	bufleft = buflen;
514	bufpkt = 0;
515}
516
517/* append packet to the buffer, flushing if necessary */
518void
519dump_packet(u_char *user, const struct pcap_pkthdr *h, const u_char *sp)
520{
521	FILE *f = (FILE *)user;
522#ifdef __FreeBSD__
523	struct pcap_sf_pkthdr sh;
524	size_t len = sizeof(sh) + h->caplen;
525#else
526	size_t len = sizeof(*h) + h->caplen;
527#endif
528
529	if (len < sizeof(*h) || h->caplen > (size_t)cur_snaplen) {
530		logmsg(LOG_NOTICE, "invalid size %u (%u/%u), packet dropped",
531		       len, cur_snaplen, snaplen);
532		packets_dropped++;
533		return;
534	}
535
536	if (len <= bufleft)
537		goto append;
538
539	if (suspended) {
540		packets_dropped++;
541		return;
542	}
543
544	if (flush_buffer(f)) {
545		packets_dropped++;
546		return;
547	}
548
549	if (len > bufleft) {
550		dump_packet_nobuf(user, h, sp);
551		return;
552	}
553
554 append:
555#ifdef __FreeBSD__
556 	sh.ts.tv_sec = (bpf_int32)h->ts.tv_sec;
557 	sh.ts.tv_usec = (bpf_int32)h->ts.tv_usec;
558	sh.caplen = h->caplen;
559	sh.len = h->len;
560
561	memcpy(bufpos, &sh, sizeof(sh));
562	memcpy(bufpos + sizeof(sh), sp, h->caplen);
563#else
564	memcpy(bufpos, h, sizeof(*h));
565	memcpy(bufpos + sizeof(*h), sp, h->caplen);
566#endif
567
568	bufpos += len;
569	bufleft -= len;
570	bufpkt++;
571
572	return;
573}
574
575int
576main(int argc, char **argv)
577{
578	struct pcap_stat pstat;
579	int ch, np, Xflag = 0;
580	pcap_handler phandler = dump_packet;
581	const char *errstr = NULL;
582
583#ifdef __FreeBSD__
584	/* another ?paranoid? safety measure we do not have */
585#else
586	closefrom(STDERR_FILENO + 1);
587#endif
588
589	while ((ch = getopt(argc, argv, "Dxd:f:i:s:")) != -1) {
590		switch (ch) {
591		case 'D':
592			Debug = 1;
593			break;
594		case 'd':
595			delay = strtonum(optarg, 5, 60*60, &errstr);
596			if (errstr)
597				usage();
598			break;
599		case 'f':
600			filename = optarg;
601			break;
602		case 'i':
603			interface = optarg;
604			break;
605		case 's':
606			snaplen = strtonum(optarg, 0, PFLOGD_MAXSNAPLEN,
607			    &errstr);
608			if (snaplen <= 0)
609				snaplen = DEF_SNAPLEN;
610			if (errstr)
611				snaplen = PFLOGD_MAXSNAPLEN;
612			break;
613		case 'x':
614			Xflag++;
615			break;
616		default:
617			usage();
618		}
619
620	}
621
622	log_debug = Debug;
623	argc -= optind;
624	argv += optind;
625
626	if (!Debug) {
627		openlog("pflogd", LOG_PID | LOG_CONS, LOG_DAEMON);
628		if (daemon(0, 0)) {
629			logmsg(LOG_WARNING, "Failed to become daemon: %s",
630			    strerror(errno));
631		}
632		pidfile(NULL);
633	}
634
635	tzset();
636	(void)umask(S_IRWXG | S_IRWXO);
637
638	/* filter will be used by the privileged process */
639	if (argc) {
640		filter = copy_argv(argv);
641		if (filter == NULL)
642			logmsg(LOG_NOTICE, "Failed to form filter expression");
643	}
644
645	/* initialize pcap before dropping privileges */
646	if (init_pcap()) {
647		logmsg(LOG_ERR, "Exiting, init failure");
648		exit(1);
649	}
650
651	/* Privilege separation begins here */
652	if (priv_init()) {
653		logmsg(LOG_ERR, "unable to privsep");
654		exit(1);
655	}
656
657	setproctitle("[initializing]");
658	/* Process is now unprivileged and inside a chroot */
659	signal(SIGTERM, sig_close);
660	signal(SIGINT, sig_close);
661	signal(SIGQUIT, sig_close);
662	signal(SIGALRM, sig_alrm);
663	signal(SIGHUP, sig_hup);
664	alarm(delay);
665
666	buffer = malloc(PFLOGD_BUFSIZE);
667
668	if (buffer == NULL) {
669		logmsg(LOG_WARNING, "Failed to allocate output buffer");
670		phandler = dump_packet_nobuf;
671	} else {
672		bufleft = buflen = PFLOGD_BUFSIZE;
673		bufpos = buffer;
674		bufpkt = 0;
675	}
676
677	if (reset_dump(Xflag) < 0) {
678		if (Xflag)
679			return (1);
680
681		logmsg(LOG_ERR, "Logging suspended: open error");
682		set_suspended(1);
683	} else if (Xflag)
684		return (0);
685
686	while (1) {
687		np = pcap_dispatch(hpcap, PCAP_NUM_PKTS,
688		    phandler, (u_char *)dpcap);
689		if (np < 0) {
690#ifdef __FreeBSD__
691			if (errno == ENXIO) {
692				logmsg(LOG_ERR,
693				    "Device not/no longer configured");
694				break;
695			}
696#endif
697			logmsg(LOG_NOTICE, "%s", pcap_geterr(hpcap));
698		}
699
700		if (gotsig_close)
701			break;
702		if (gotsig_hup) {
703			if (reset_dump(0)) {
704				logmsg(LOG_ERR,
705				    "Logging suspended: open error");
706				set_suspended(1);
707			}
708			gotsig_hup = 0;
709		}
710
711		if (gotsig_alrm) {
712			if (dpcap)
713				flush_buffer(dpcap);
714			else
715				gotsig_hup = 1;
716			gotsig_alrm = 0;
717			alarm(delay);
718		}
719	}
720
721	logmsg(LOG_NOTICE, "Exiting");
722	if (dpcap) {
723		flush_buffer(dpcap);
724		fclose(dpcap);
725	}
726	purge_buffer();
727
728	if (pcap_stats(hpcap, &pstat) < 0)
729		logmsg(LOG_WARNING, "Reading stats: %s", pcap_geterr(hpcap));
730	else
731		logmsg(LOG_NOTICE,
732		    "%u packets received, %u/%u dropped (kernel/pflogd)",
733		    pstat.ps_recv, pstat.ps_drop, packets_dropped);
734
735	pcap_close(hpcap);
736	if (!Debug)
737		closelog();
738	return (0);
739}
740