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