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