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