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