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