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