xref: /dragonfly/usr.sbin/pflogd/pflogd.c (revision dca3c15d)
1 /*	$OpenBSD: pflogd.c,v 1.27 2004/02/13 19:01:57 otto Exp $	*/
2 /*	$DragonFly: src/usr.sbin/pflogd/pflogd.c,v 1.2 2004/12/18 22:48:04 swildner Exp $ */
3 
4 /*
5  * Copyright (c) 2001 Theo de Raadt
6  * Copyright (c) 2001 Can Erkin Acar
7  * All rights reserved.
8  *
9  * Redistribution and use in source and binary forms, with or without
10  * modification, are permitted provided that the following conditions
11  * are met:
12  *
13  *    - Redistributions of source code must retain the above copyright
14  *      notice, this list of conditions and the following disclaimer.
15  *    - Redistributions in binary form must reproduce the above
16  *      copyright notice, this list of conditions and the following
17  *      disclaimer in the documentation and/or other materials provided
18  *      with the distribution.
19  *
20  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
21  * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
22  * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
23  * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
24  * COPYRIGHT HOLDERS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
25  * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
26  * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
27  * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
28  * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
29  * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
30  * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
31  * POSSIBILITY OF SUCH DAMAGE.
32  */
33 
34 #include <sys/types.h>
35 #include <sys/ioctl.h>
36 #include <sys/file.h>
37 #include <sys/stat.h>
38 #include <machine/inttypes.h>
39 
40 #include <errno.h>
41 #include <fcntl.h>
42 #include <syslog.h>
43 #include <signal.h>
44 #include <stdarg.h>
45 #include <stdio.h>
46 #include <stdlib.h>
47 #include <string.h>
48 #include <unistd.h>
49 
50 #include <libutil.h>
51 #include <pcap-int.h>
52 #include <pcap.h>
53 
54 #include "pflogd.h"
55 
56 pcap_t *hpcap;
57 static FILE *dpcap;
58 
59 int Debug = 0;
60 static int snaplen = DEF_SNAPLEN;
61 static int cur_snaplen = DEF_SNAPLEN;
62 
63 volatile sig_atomic_t gotsig_close, gotsig_alrm, gotsig_hup;
64 
65 const char *filename = PFLOGD_LOG_FILE;
66 const char *interface = PFLOGD_DEFAULT_IF;
67 char *filter = NULL;
68 
69 char errbuf[PCAP_ERRBUF_SIZE];
70 
71 int log_debug = 0;
72 unsigned int delay = FLUSH_DELAY;
73 
74 char *copy_argv(char * const *);
75 void  dump_packet(u_char *, const struct pcap_pkthdr *, const u_char *);
76 void  dump_packet_nobuf(u_char *, const struct pcap_pkthdr *, const u_char *);
77 int   flush_buffer(FILE *);
78 int   init_pcap(void);
79 void  purge_buffer(void);
80 int   reset_dump(void);
81 int   scan_dump(FILE *, off_t);
82 int   set_snaplen(int);
83 void  set_suspended(int);
84 void  sig_alrm(int);
85 void  sig_close(int);
86 void  sig_hup(int);
87 void  usage(void);
88 
89 /* buffer must always be greater than snaplen */
90 static int    bufpkt = 0;	/* number of packets in buffer */
91 static size_t buflen = 0;	/* allocated size of buffer */
92 static char  *buffer = NULL;	/* packet buffer */
93 static char  *bufpos = NULL;	/* position in buffer */
94 static size_t bufleft = 0;	/* bytes left in buffer */
95 
96 /* if error, stop logging but count dropped packets */
97 static int suspended = -1;
98 static long packets_dropped = 0;
99 
100 void
101 set_suspended(int s)
102 {
103 	if (suspended == s)
104 		return;
105 
106 	suspended = s;
107 	setproctitle("[%s] -s %d -f %s",
108             suspended ? "suspended" : "running", cur_snaplen, filename);
109 }
110 
111 char *
112 copy_argv(char * const *argv)
113 {
114 	size_t len = 0, n;
115 	char *buf;
116 
117 	if (argv == NULL)
118 		return (NULL);
119 
120 	for (n = 0; argv[n]; n++)
121 		len += strlen(argv[n])+1;
122 	if (len == 0)
123 		return (NULL);
124 
125 	buf = malloc(len);
126 	if (buf == NULL)
127 		return (NULL);
128 
129 	strlcpy(buf, argv[0], len);
130 	for (n = 1; argv[n]; n++) {
131 		strlcat(buf, " ", len);
132 		strlcat(buf, argv[n], len);
133 	}
134 	return (buf);
135 }
136 
137 void
138 logmsg(int pri, const char *message, ...)
139 {
140 	va_list ap;
141 	va_start(ap, message);
142 
143 	if (log_debug) {
144 		vfprintf(stderr, message, ap);
145 		fprintf(stderr, "\n");
146 	} else
147 		vsyslog(pri, message, ap);
148 	va_end(ap);
149 }
150 
151 void
152 usage(void)
153 {
154 	fprintf(stderr, "usage: pflogd [-Dx] [-d delay] [-f filename] ");
155 	fprintf(stderr, "[-s snaplen] [expression]\n");
156 	exit(1);
157 }
158 
159 void
160 sig_close(int sig __unused)
161 {
162 	gotsig_close = 1;
163 }
164 
165 void
166 sig_hup(int sig __unused)
167 {
168 	gotsig_hup = 1;
169 }
170 
171 void
172 sig_alrm(int sig __unused)
173 {
174 	gotsig_alrm = 1;
175 }
176 
177 void
178 set_pcap_filter(void)
179 {
180 	struct bpf_program bprog;
181 
182 	if (pcap_compile(hpcap, &bprog, filter, PCAP_OPT_FIL, 0) < 0)
183 		logmsg(LOG_WARNING, "%s", pcap_geterr(hpcap));
184 	else {
185 		if (pcap_setfilter(hpcap, &bprog) < 0)
186 			logmsg(LOG_WARNING, "%s", pcap_geterr(hpcap));
187 		pcap_freecode(&bprog);
188 	}
189 }
190 
191 int
192 init_pcap(void)
193 {
194 	hpcap = pcap_open_live(interface, snaplen, 1, PCAP_TO_MS, errbuf);
195 	if (hpcap == NULL) {
196 		logmsg(LOG_ERR, "Failed to initialize: %s", errbuf);
197 		return (-1);
198 	}
199 
200 	if (pcap_datalink(hpcap) != DLT_PFLOG) {
201 		logmsg(LOG_ERR, "Invalid datalink type");
202 		pcap_close(hpcap);
203 		hpcap = NULL;
204 		return (-1);
205 	}
206 
207 	set_pcap_filter();
208 
209 	cur_snaplen = snaplen = pcap_snapshot(hpcap);
210 
211 	/* From contrib/pf/pflogd.c 1.5 FreeBSD: BPF locking is not
212 	 * (yet) supported.
213 	 */
214 	#ifndef __DragonFly__
215 	/* lock */
216 	if (ioctl(pcap_fileno(hpcap), BIOCLOCK) < 0) {
217 		logmsg(LOG_ERR, "BIOCLOCK: %s", strerror(errno));
218 		return (-1);
219 	}
220 	#endif
221 
222 	return (0);
223 }
224 
225 int
226 set_snaplen(int snap)
227 {
228 	if (priv_set_snaplen(snap))
229 		return (1);
230 
231 	if (cur_snaplen > snap)
232 		purge_buffer();
233 
234 	cur_snaplen = snap;
235 
236 	return (0);
237 }
238 
239 int
240 reset_dump(void)
241 {
242 	struct pcap_file_header hdr;
243 	struct stat st;
244 	int fd;
245 	FILE *fp;
246 
247 	if (hpcap == NULL)
248 		return (-1);
249 
250 	if (dpcap) {
251 		flush_buffer(dpcap);
252 		fclose(dpcap);
253 		dpcap = NULL;
254 	}
255 
256 	/*
257 	 * Basically reimplement pcap_dump_open() because it truncates
258 	 * files and duplicates headers and such.
259 	 */
260 	fd = priv_open_log();
261 	if (fd < 0)
262 		return (1);
263 
264 	fp = fdopen(fd, "a+");
265 
266 	if (fp == NULL) {
267 		logmsg(LOG_ERR, "Error: %s: %s", filename, strerror(errno));
268 		return (1);
269 	}
270 	if (fstat(fileno(fp), &st) == -1) {
271 		logmsg(LOG_ERR, "Error: %s: %s", filename, strerror(errno));
272 		return (1);
273 	}
274 
275 	/* set FILE unbuffered, we do our own buffering */
276 	if (setvbuf(fp, NULL, _IONBF, 0)) {
277 		logmsg(LOG_ERR, "Failed to set output buffers");
278 		return (1);
279 	}
280 
281 #define TCPDUMP_MAGIC 0xa1b2c3d4
282 
283 	if (st.st_size == 0) {
284 		if (snaplen != cur_snaplen) {
285 			logmsg(LOG_NOTICE, "Using snaplen %d", snaplen);
286 			if (set_snaplen(snaplen)) {
287 				logmsg(LOG_WARNING,
288 				    "Failed, using old settings");
289 			}
290 		}
291 		hdr.magic = TCPDUMP_MAGIC;
292 		hdr.version_major = PCAP_VERSION_MAJOR;
293 		hdr.version_minor = PCAP_VERSION_MINOR;
294 		hdr.thiszone = hpcap->tzoff;
295 		hdr.snaplen = hpcap->snapshot;
296 		hdr.sigfigs = 0;
297 		hdr.linktype = hpcap->linktype;
298 
299 		if (fwrite((char *)&hdr, sizeof(hdr), 1, fp) != 1) {
300 			fclose(fp);
301 			return (1);
302 		}
303 	} else if (scan_dump(fp, st.st_size)) {
304 		/* XXX move file and continue? */
305 		fclose(fp);
306 		return (1);
307 	}
308 
309 	dpcap = fp;
310 
311 	set_suspended(0);
312 	flush_buffer(fp);
313 
314 	return (0);
315 }
316 
317 int
318 scan_dump(FILE *fp, off_t size)
319 {
320 	struct pcap_file_header hdr;
321 	struct pcap_pkthdr ph;
322 	off_t pos;
323 
324 	/*
325 	 * Must read the file, compare the header against our new
326 	 * options (in particular, snaplen) and adjust our options so
327 	 * that we generate a correct file. Furthermore, check the file
328 	 * for consistency so that we can append safely.
329 	 *
330 	 * XXX this may take a long time for large logs.
331 	 */
332 	fseek(fp, 0L, SEEK_SET);
333 
334 	if (fread((char *)&hdr, sizeof(hdr), 1, fp) != 1) {
335 		logmsg(LOG_ERR, "Short file header");
336 		return (1);
337 	}
338 
339 	if (hdr.magic != TCPDUMP_MAGIC ||
340 	    hdr.version_major != PCAP_VERSION_MAJOR ||
341 	    hdr.version_minor != PCAP_VERSION_MINOR ||
342 	    (int)hdr.linktype != hpcap->linktype ||
343 	    hdr.snaplen > PFLOGD_MAXSNAPLEN) {
344 		logmsg(LOG_ERR, "Invalid/incompatible log file, move it away");
345 		return (1);
346 	}
347 
348 	pos = sizeof(hdr);
349 
350 	while (!feof(fp)) {
351 		off_t len = fread((char *)&ph, 1, sizeof(ph), fp);
352 		if (len == 0)
353 			break;
354 
355 		if (len != sizeof(ph))
356 			goto error;
357 		if (ph.caplen > hdr.snaplen || ph.caplen > PFLOGD_MAXSNAPLEN)
358 			goto error;
359 		pos += sizeof(ph) + ph.caplen;
360 		if (pos > size)
361 			goto error;
362 		fseek(fp, ph.caplen, SEEK_CUR);
363 	}
364 
365 	if (pos != size)
366 		goto error;
367 
368 	if ((int)hdr.snaplen != cur_snaplen) {
369 		logmsg(LOG_WARNING,
370 		       "Existing file has different snaplen %u, using it",
371 		       hdr.snaplen);
372 		if (set_snaplen(hdr.snaplen)) {
373 			logmsg(LOG_WARNING,
374 			       "Failed, using old settings, offset %llu",
375 			       (unsigned long long) size);
376 		}
377 	}
378 
379 	return (0);
380 
381  error:
382 	logmsg(LOG_ERR, "Corrupted log file.");
383 	return (1);
384 }
385 
386 /* dump a packet directly to the stream, which is unbuffered */
387 void
388 dump_packet_nobuf(u_char *user, const struct pcap_pkthdr *h, const u_char *sp)
389 {
390 	FILE *f = (FILE *)user;
391 
392 	if (suspended) {
393 		packets_dropped++;
394 		return;
395 	}
396 
397 	if (fwrite(h, sizeof(*h), 1, f) != 1) {
398 		/* try to undo header to prevent corruption */
399 		off_t pos = ftello(f);
400 		if ((size_t)pos < sizeof(*h) ||
401 		    ftruncate(fileno(f), pos - sizeof(*h))) {
402 			logmsg(LOG_ERR, "Write failed, corrupted logfile!");
403 			set_suspended(1);
404 			gotsig_close = 1;
405 			return;
406 		}
407 		goto error;
408 	}
409 
410 	if (fwrite(sp, h->caplen, 1, f) != 1)
411 		goto error;
412 
413 	return;
414 
415 error:
416 	set_suspended(1);
417 	packets_dropped ++;
418 	logmsg(LOG_ERR, "Logging suspended: fwrite: %s", strerror(errno));
419 }
420 
421 int
422 flush_buffer(FILE *f)
423 {
424 	off_t offset;
425 	int len = bufpos - buffer;
426 
427 	if (len <= 0)
428 		return (0);
429 
430 	offset = ftello(f);
431 	if (offset == (off_t)-1) {
432 		set_suspended(1);
433 		logmsg(LOG_ERR, "Logging suspended: ftello: %s",
434 		    strerror(errno));
435 		return (1);
436 	}
437 
438 	if (fwrite(buffer, len, 1, f) != 1) {
439 		set_suspended(1);
440 		logmsg(LOG_ERR, "Logging suspended: fwrite: %s",
441 		    strerror(errno));
442 		ftruncate(fileno(f), offset);
443 		return (1);
444 	}
445 
446 	set_suspended(0);
447 	bufpos = buffer;
448 	bufleft = buflen;
449 	bufpkt = 0;
450 
451 	return (0);
452 }
453 
454 void
455 purge_buffer(void)
456 {
457 	packets_dropped += bufpkt;
458 
459 	set_suspended(0);
460 	bufpos = buffer;
461 	bufleft = buflen;
462 	bufpkt = 0;
463 }
464 
465 /* append packet to the buffer, flushing if necessary */
466 void
467 dump_packet(u_char *user, const struct pcap_pkthdr *h, const u_char *sp)
468 {
469 	FILE *f = (FILE *)user;
470 	size_t len = sizeof(*h) + h->caplen;
471 
472 	if (len < sizeof(*h) || h->caplen > (size_t)cur_snaplen) {
473 		logmsg(LOG_NOTICE, "invalid size %u (%u/%u), packet dropped",
474 		       len, cur_snaplen, snaplen);
475 		packets_dropped++;
476 		return;
477 	}
478 
479 	if (len <= bufleft)
480 		goto append;
481 
482 	if (suspended) {
483 		packets_dropped++;
484 		return;
485 	}
486 
487 	if (flush_buffer(f)) {
488 		packets_dropped++;
489 		return;
490 	}
491 
492 	if (len > bufleft) {
493 		dump_packet_nobuf(user, h, sp);
494 		return;
495 	}
496 
497  append:
498 	memcpy(bufpos, h, sizeof(*h));
499 	memcpy(bufpos + sizeof(*h), sp, h->caplen);
500 
501 	bufpos += len;
502 	bufleft -= len;
503 	bufpkt++;
504 
505 	return;
506 }
507 
508 int
509 main(int argc, char **argv)
510 {
511 	struct pcap_stat pstat;
512 	int ch, np, Xflag = 0;
513 	pcap_handler phandler = dump_packet;
514 
515 	/* Neither FreeBSD nor DFly have this; Max seems to think this may
516 	 * be a paranoid check. Comment it out:
517 	closefrom(STDERR_FILENO + 1);
518 	 */
519 
520 	while ((ch = getopt(argc, argv, "Dxd:s:f:")) != -1) {
521 		switch (ch) {
522 		case 'D':
523 			Debug = 1;
524 			break;
525 		case 'd':
526 			delay = atoi(optarg);
527 			if (delay < 5 || delay > 60*60)
528 				usage();
529 			break;
530 		case 'f':
531 			filename = optarg;
532 			break;
533 		case 's':
534 			snaplen = atoi(optarg);
535 			if (snaplen <= 0)
536 				snaplen = DEF_SNAPLEN;
537 			if (snaplen > PFLOGD_MAXSNAPLEN)
538 				snaplen = PFLOGD_MAXSNAPLEN;
539 			break;
540 		case 'x':
541 			Xflag++;
542 			break;
543 		default:
544 			usage();
545 		}
546 
547 	}
548 
549 	log_debug = Debug;
550 	argc -= optind;
551 	argv += optind;
552 
553 	if (!Debug) {
554 		openlog("pflogd", LOG_PID | LOG_CONS, LOG_DAEMON);
555 		if (daemon(0, 0)) {
556 			logmsg(LOG_WARNING, "Failed to become daemon: %s",
557 			    strerror(errno));
558 		}
559 		pidfile(NULL);
560 	}
561 
562 	umask(S_IRWXG | S_IRWXO);
563 
564 	/* filter will be used by the privileged process */
565 	if (argc) {
566 		filter = copy_argv(argv);
567 		if (filter == NULL)
568 			logmsg(LOG_NOTICE, "Failed to form filter expression");
569 	}
570 
571 	/* initialize pcap before dropping privileges */
572 	if (init_pcap()) {
573 		logmsg(LOG_ERR, "Exiting, init failure");
574 		exit(1);
575 	}
576 
577 	/* Privilege separation begins here */
578 	if (priv_init()) {
579 		logmsg(LOG_ERR, "unable to privsep");
580 		exit(1);
581 	}
582 
583 	setproctitle("[initializing]");
584 	/* Process is now unprivileged and inside a chroot */
585 	signal(SIGTERM, sig_close);
586 	signal(SIGINT, sig_close);
587 	signal(SIGQUIT, sig_close);
588 	signal(SIGALRM, sig_alrm);
589 	signal(SIGHUP, sig_hup);
590 	alarm(delay);
591 
592 	buffer = malloc(PFLOGD_BUFSIZE);
593 
594 	if (buffer == NULL) {
595 		logmsg(LOG_WARNING, "Failed to allocate output buffer");
596 		phandler = dump_packet_nobuf;
597 	} else {
598 		bufleft = buflen = PFLOGD_BUFSIZE;
599 		bufpos = buffer;
600 		bufpkt = 0;
601 	}
602 
603 	if (reset_dump()) {
604 		if (Xflag)
605 			return (1);
606 
607 		logmsg(LOG_ERR, "Logging suspended: open error");
608 		set_suspended(1);
609 	} else if (Xflag)
610 		return (0);
611 
612 	while (1) {
613 		np = pcap_dispatch(hpcap, PCAP_NUM_PKTS,
614 		    dump_packet, (u_char *)dpcap);
615 		if (np < 0)
616 			logmsg(LOG_NOTICE, "%s", pcap_geterr(hpcap));
617 
618 		if (gotsig_close)
619 			break;
620 		if (gotsig_hup) {
621 			if (reset_dump()) {
622 				logmsg(LOG_ERR,
623 				    "Logging suspended: open error");
624 				set_suspended(1);
625 			}
626 			gotsig_hup = 0;
627 		}
628 
629 		if (gotsig_alrm) {
630 			if (dpcap)
631 				flush_buffer(dpcap);
632 			gotsig_alrm = 0;
633 			alarm(delay);
634 		}
635 	}
636 
637 	logmsg(LOG_NOTICE, "Exiting");
638 	if (dpcap) {
639 		flush_buffer(dpcap);
640 		fclose(dpcap);
641 	}
642 	purge_buffer();
643 
644 	if (pcap_stats(hpcap, &pstat) < 0)
645 		logmsg(LOG_WARNING, "Reading stats: %s", pcap_geterr(hpcap));
646 	else
647 		logmsg(LOG_NOTICE,
648 		    "%u packets received, %u/%u dropped (kernel/pflogd)",
649 		    pstat.ps_recv, pstat.ps_drop, packets_dropped);
650 
651 	pcap_close(hpcap);
652 	if (!Debug)
653 		closelog();
654 	return (0);
655 }
656