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