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