+ char *filename = malloc(PATH_MAX + 1);
+ if (filename == NULL)
+ error("%s: malloc", __func__);
+ if (strlen(orig_name) == 0)
+ error("an empty string is not a valid file name");
+
+ /* Process with strftime if Gflag is set. */
+ if (Gflag != 0) {
+ struct tm *local_tm;
+
+ /* Convert Gflag_time to a usable format */
+ if ((local_tm = localtime(&Gflag_time)) == NULL) {
+ error("%s: localtime", __func__);
+ }
+
+ /* There's no good way to detect an error in strftime since a return
+ * value of 0 isn't necessarily failure; if orig_name is an empty
+ * string, the formatted string will be empty.
+ *
+ * However, the C90 standard says that, if there *is* a
+ * buffer overflow, the content of the buffer is undefined,
+ * so we must check for a buffer overflow.
+ *
+ * So we check above for an empty orig_name, and only call
+ * strftime() if it's non-empty, in which case the return
+ * value will only be 0 if the formatted date doesn't fit
+ * in the buffer.
+ *
+ * (We check above because, even if we don't use -G, we
+ * want a better error message than "tcpdump: : No such
+ * file or directory" for this case.)
+ */
+ if (strftime(filename, PATH_MAX, orig_name, local_tm) == 0) {
+ error("%s: strftime", __func__);
+ }
+ } else {
+ strncpy(filename, orig_name, PATH_MAX);
+ }
+
+ if (cnt == 0 && max_chars == 0)
+ strncpy(buffer, filename, PATH_MAX + 1);
+ else
+ if (snprintf(buffer, PATH_MAX + 1, "%s%0*d", filename, max_chars, cnt) > PATH_MAX)
+ /* Report an error if the filename is too large */
+ error("too many output files or filename is too long (> %d)", PATH_MAX);
+ free(filename);
+}
+
+static char *
+get_next_file(FILE *VFile, char *ptr)
+{
+ char *ret;
+ size_t len;
+
+ ret = fgets(ptr, PATH_MAX, VFile);
+ if (!ret)
+ return NULL;
+
+ len = strlen (ptr);
+ if (len > 0 && ptr[len - 1] == '\n')
+ ptr[len - 1] = '\0';
+
+ return ret;
+}
+
+#ifdef HAVE_CASPER
+static cap_channel_t *
+capdns_setup(void)
+{
+ cap_channel_t *capcas, *capdnsloc;
+ const char *types[1];
+ int families[2];
+
+ capcas = cap_init();
+ if (capcas == NULL)
+ error("unable to create casper process");
+ capdnsloc = cap_service_open(capcas, "system.dns");
+ /* Casper capability no longer needed. */
+ cap_close(capcas);
+ if (capdnsloc == NULL)
+ error("unable to open system.dns service");
+ /* Limit system.dns to reverse DNS lookups. */
+ types[0] = "ADDR";
+ if (cap_dns_type_limit(capdnsloc, types, 1) < 0)
+ error("unable to limit access to system.dns service");
+ families[0] = AF_INET;
+ families[1] = AF_INET6;
+ if (cap_dns_family_limit(capdnsloc, families, 2) < 0)
+ error("unable to limit access to system.dns service");
+
+ return (capdnsloc);
+}
+#endif /* HAVE_CASPER */
+
+#ifdef HAVE_PCAP_SET_TSTAMP_PRECISION
+static int
+tstamp_precision_from_string(const char *precision)
+{
+ if (strncmp(precision, "nano", strlen("nano")) == 0)
+ return PCAP_TSTAMP_PRECISION_NANO;
+
+ if (strncmp(precision, "micro", strlen("micro")) == 0)
+ return PCAP_TSTAMP_PRECISION_MICRO;
+
+ return -EINVAL;
+}
+
+static const char *
+tstamp_precision_to_string(int precision)
+{
+ switch (precision) {
+
+ case PCAP_TSTAMP_PRECISION_MICRO:
+ return "micro";
+
+ case PCAP_TSTAMP_PRECISION_NANO:
+ return "nano";
+
+ default:
+ return "unknown";
+ }
+}
+#endif
+
+#ifdef HAVE_CAPSICUM
+/*
+ * Ensure that, on a dump file's descriptor, we have all the rights
+ * necessary to make the standard I/O library work with an fdopen()ed
+ * FILE * from that descriptor.
+ *
+ * A long time ago in a galaxy far, far away, AT&T decided that, instead
+ * of providing separate APIs for getting and setting the FD_ flags on a
+ * descriptor, getting and setting the O_ flags on a descriptor, and
+ * locking files, they'd throw them all into a kitchen-sink fcntl() call
+ * along the lines of ioctl(), the fact that ioctl() operations are
+ * largely specific to particular character devices but fcntl() operations
+ * are either generic to all descriptors or generic to all descriptors for
+ * regular files notwithstanding.
+ *
+ * The Capsicum people decided that fine-grained control of descriptor
+ * operations was required, so that you need to grant permission for
+ * reading, writing, seeking, and fcntl-ing. The latter, courtesy of
+ * AT&T's decision, means that "fcntl-ing" isn't a thing, but a motley
+ * collection of things, so there are *individual* fcntls for which
+ * permission needs to be granted.
+ *
+ * The FreeBSD standard I/O people implemented some optimizations that
+ * requires that the standard I/O routines be able to determine whether
+ * the descriptor for the FILE * is open append-only or not; as that
+ * descriptor could have come from an open() rather than an fopen(),
+ * that requires that it be able to do an F_GETFL fcntl() to read
+ * the O_ flags.
+ *
+ * Tcpdump uses ftell() to determine how much data has been written
+ * to a file in order to, when used with -C, determine when it's time
+ * to rotate capture files. ftell() therefore needs to do an lseek()
+ * to find out the file offset and must, thanks to the aforementioned
+ * optimization, also know whether the descriptor is open append-only
+ * or not.
+ *
+ * The net result of all the above is that we need to grant CAP_SEEK,
+ * CAP_WRITE, and CAP_FCNTL with the CAP_FCNTL_GETFL subcapability.
+ *
+ * Perhaps this is the universe's way of saying that either
+ *
+ * 1) there needs to be an fopenat() call and a pcap_dump_openat() call
+ * using it, so that Capsicum-capable tcpdump wouldn't need to do
+ * an fdopen()
+ *
+ * or
+ *
+ * 2) there needs to be a cap_fdopen() call in the FreeBSD standard
+ * I/O library that knows what rights are needed by the standard
+ * I/O library, based on the open mode, and assigns them, perhaps
+ * with an additional argument indicating, for example, whether
+ * seeking should be allowed, so that tcpdump doesn't need to know
+ * what the standard I/O library happens to require this week.
+ */
+static void
+set_dumper_capsicum_rights(pcap_dumper_t *p)
+{
+ int fd = fileno(pcap_dump_file(p));
+ cap_rights_t rights;
+
+ cap_rights_init(&rights, CAP_SEEK, CAP_WRITE, CAP_FCNTL);
+ if (cap_rights_limit(fd, &rights) < 0 && errno != ENOSYS) {
+ error("unable to limit dump descriptor");
+ }
+ if (cap_fcntls_limit(fd, CAP_FCNTL_GETFL) < 0 && errno != ENOSYS) {
+ error("unable to limit dump descriptor fcntls");
+ }
+}
+#endif
+
+/*
+ * Copy arg vector into a new buffer, concatenating arguments with spaces.
+ */
+static char *
+copy_argv(char **argv)
+{
+ char **p;
+ size_t len = 0;
+ char *buf;
+ char *src, *dst;
+
+ p = argv;
+ if (*p == NULL)
+ return 0;
+
+ while (*p)
+ len += strlen(*p++) + 1;
+
+ buf = (char *)malloc(len);
+ if (buf == NULL)
+ error("%s: malloc", __func__);
+
+ p = argv;
+ dst = buf;
+ while ((src = *p++) != NULL) {
+ while ((*dst++ = *src++) != '\0')
+ ;
+ dst[-1] = ' ';
+ }
+ dst[-1] = '\0';
+
+ return buf;
+}
+
+/*
+ * On Windows, we need to open the file in binary mode, so that
+ * we get all the bytes specified by the size we get from "fstat()".
+ * On UNIX, that's not necessary. O_BINARY is defined on Windows;
+ * we define it as 0 if it's not defined, so it does nothing.
+ */
+#ifndef O_BINARY
+#define O_BINARY 0
+#endif
+
+static char *
+read_infile(char *fname)
+{
+ int i, fd;
+ ssize_t cc;
+ char *cp;
+ our_statb buf;
+
+ fd = open(fname, O_RDONLY|O_BINARY);
+ if (fd < 0)
+ error("can't open %s: %s", fname, pcap_strerror(errno));
+
+ if (our_fstat(fd, &buf) < 0)
+ error("can't stat %s: %s", fname, pcap_strerror(errno));
+
+ /*
+ * Reject files whose size doesn't fit into an int; a filter
+ * *that* large will probably be too big.
+ */
+ if (buf.st_size > INT_MAX)
+ error("%s is too large", fname);
+
+ cp = malloc((u_int)buf.st_size + 1);
+ if (cp == NULL)
+ error("malloc(%d) for %s: %s", (u_int)buf.st_size + 1,
+ fname, pcap_strerror(errno));
+ cc = read(fd, cp, (u_int)buf.st_size);
+ if (cc < 0)
+ error("read %s: %s", fname, pcap_strerror(errno));
+ if (cc != buf.st_size)
+ error("short read %s (%d != %d)", fname, (int) cc,
+ (int)buf.st_size);
+
+ close(fd);
+ /* replace "# comment" with spaces */
+ for (i = 0; i < cc; i++) {
+ if (cp[i] == '#')
+ while (i < cc && cp[i] != '\n')
+ cp[i++] = ' ';
+ }
+ cp[cc] = '\0';
+ return (cp);
+}
+
+#ifdef HAVE_PCAP_FINDALLDEVS
+static long
+parse_interface_number(const char *device)
+{
+ const char *p;
+ long devnum;
+ char *end;
+
+ /*
+ * Search for a colon, terminating any scheme at the beginning
+ * of the device.
+ */
+ p = strchr(device, ':');
+ if (p != NULL) {
+ /*
+ * We found it. Is it followed by "//"?
+ */
+ p++; /* skip the : */
+ if (strncmp(p, "//", 2) == 0) {
+ /*
+ * Yes. Search for the next /, at the end of the
+ * authority part of the URL.
+ */
+ p += 2; /* skip the // */
+ p = strchr(p, '/');
+ if (p != NULL) {
+ /*
+ * OK, past the / is the path.
+ */
+ device = p + 1;
+ }
+ }
+ }
+ devnum = strtol(device, &end, 10);
+ if (device != end && *end == '\0') {
+ /*
+ * It's all-numeric, but is it a valid number?
+ */
+ if (devnum <= 0) {
+ /*
+ * No, it's not an ordinal.
+ */
+ error("Invalid adapter index %s", device);
+ }
+ return (devnum);
+ } else {
+ /*
+ * It's not all-numeric; return -1, so our caller
+ * knows that.
+ */
+ return (-1);
+ }
+}
+
+static char *
+find_interface_by_number(const char *url
+#ifndef HAVE_PCAP_FINDALLDEVS_EX
+_U_
+#endif
+, long devnum)
+{
+ pcap_if_t *dev, *devlist;
+ long i;
+ char ebuf[PCAP_ERRBUF_SIZE];
+ char *device;
+#ifdef HAVE_PCAP_FINDALLDEVS_EX
+ const char *endp;
+ char *host_url;
+#endif
+ int status;
+
+#ifdef HAVE_PCAP_FINDALLDEVS_EX
+ /*
+ * Search for a colon, terminating any scheme at the beginning
+ * of the URL.
+ */
+ endp = strchr(url, ':');
+ if (endp != NULL) {
+ /*
+ * We found it. Is it followed by "//"?
+ */
+ endp++; /* skip the : */
+ if (strncmp(endp, "//", 2) == 0) {
+ /*
+ * Yes. Search for the next /, at the end of the
+ * authority part of the URL.
+ */
+ endp += 2; /* skip the // */
+ endp = strchr(endp, '/');
+ } else
+ endp = NULL;
+ }
+ if (endp != NULL) {
+ /*
+ * OK, everything from device to endp is a URL to hand
+ * to pcap_findalldevs_ex().
+ */
+ endp++; /* Include the trailing / in the URL; pcap_findalldevs_ex() requires it */
+ host_url = malloc(endp - url + 1);
+ if (host_url == NULL && (endp - url + 1) > 0)
+ error("Invalid allocation for host");
+
+ memcpy(host_url, url, endp - url);
+ host_url[endp - url] = '\0';
+ status = pcap_findalldevs_ex(host_url, NULL, &devlist, ebuf);
+ free(host_url);
+ } else
+#endif
+ status = pcap_findalldevs(&devlist, ebuf);
+ if (status < 0)
+ error("%s", ebuf);
+ /*
+ * Look for the devnum-th entry in the list of devices (1-based).
+ */
+ for (i = 0, dev = devlist; i < devnum-1 && dev != NULL;
+ i++, dev = dev->next)
+ ;
+ if (dev == NULL) {
+ pcap_freealldevs(devlist);
+ error("Invalid adapter index %ld: only %ld interfaces found",
+ devnum, i);
+ }
+ device = strdup(dev->name);
+ pcap_freealldevs(devlist);
+ return (device);
+}
+#endif
+
+#ifdef HAVE_PCAP_OPEN
+/*
+ * Prefixes for rpcap URLs.
+ */
+static char rpcap_prefix[] = "rpcap://";
+static char rpcap_ssl_prefix[] = "rpcaps://";
+#endif
+
+static pcap_t *
+open_interface(const char *device, netdissect_options *ndo, char *ebuf)
+{
+ pcap_t *pc;
+#ifdef HAVE_PCAP_CREATE
+ int status;
+ char *cp;
+#endif
+
+#ifdef HAVE_PCAP_OPEN
+ /*
+ * Is this an rpcap URL?
+ */
+ if (strncmp(device, rpcap_prefix, sizeof(rpcap_prefix) - 1) == 0 ||
+ strncmp(device, rpcap_ssl_prefix, sizeof(rpcap_ssl_prefix) - 1) == 0) {
+ /*
+ * Yes. Open it with pcap_open().
+ */
+ *ebuf = '\0';
+ pc = pcap_open(device, ndo->ndo_snaplen,
+ pflag ? 0 : PCAP_OPENFLAG_PROMISCUOUS, timeout, NULL,
+ ebuf);
+ if (pc == NULL) {
+ /*
+ * If this failed with "No such device" or "The system
+ * cannot find the device specified", that means
+ * the interface doesn't exist; return NULL, so that
+ * the caller can see whether the device name is
+ * actually an interface index.
+ */
+ if (strstr(ebuf, "No such device") != NULL ||
+ strstr(ebuf, "The system cannot find the device specified") != NULL)
+ return (NULL);
+ error("%s", ebuf);
+ }
+ if (*ebuf)
+ warning("%s", ebuf);
+ return (pc);
+ }
+#endif /* HAVE_PCAP_OPEN */
+
+#ifdef HAVE_PCAP_CREATE
+ pc = pcap_create(device, ebuf);
+ if (pc == NULL) {
+ /*
+ * If this failed with "No such device", that means
+ * the interface doesn't exist; return NULL, so that
+ * the caller can see whether the device name is
+ * actually an interface index.
+ */
+ if (strstr(ebuf, "No such device") != NULL)
+ return (NULL);
+ error("%s", ebuf);
+ }
+#ifdef HAVE_PCAP_SET_TSTAMP_TYPE
+ if (Jflag)
+ show_tstamp_types_and_exit(pc, device);
+#endif
+#ifdef HAVE_PCAP_SET_TSTAMP_PRECISION
+ status = pcap_set_tstamp_precision(pc, ndo->ndo_tstamp_precision);
+ if (status != 0)
+ error("%s: Can't set %ssecond time stamp precision: %s",
+ device,
+ tstamp_precision_to_string(ndo->ndo_tstamp_precision),
+ pcap_statustostr(status));
+#endif
+
+#ifdef HAVE_PCAP_SET_IMMEDIATE_MODE
+ if (immediate_mode) {
+ status = pcap_set_immediate_mode(pc, 1);
+ if (status != 0)
+ error("%s: Can't set immediate mode: %s",
+ device, pcap_statustostr(status));
+ }
+#endif
+ /*
+ * Is this an interface that supports monitor mode?
+ */
+ if (pcap_can_set_rfmon(pc) == 1)
+ supports_monitor_mode = 1;
+ else
+ supports_monitor_mode = 0;
+ if (ndo->ndo_snaplen != 0) {
+ /*
+ * A snapshot length was explicitly specified;
+ * use it.
+ */
+ status = pcap_set_snaplen(pc, ndo->ndo_snaplen);
+ if (status != 0)
+ error("%s: Can't set snapshot length: %s",
+ device, pcap_statustostr(status));
+ }
+ status = pcap_set_promisc(pc, !pflag);
+ if (status != 0)
+ error("%s: Can't set promiscuous mode: %s",
+ device, pcap_statustostr(status));
+ if (Iflag) {
+ status = pcap_set_rfmon(pc, 1);
+ if (status != 0)
+ error("%s: Can't set monitor mode: %s",
+ device, pcap_statustostr(status));
+ }
+ status = pcap_set_timeout(pc, timeout);
+ if (status != 0)
+ error("%s: pcap_set_timeout failed: %s",
+ device, pcap_statustostr(status));
+ if (Bflag != 0) {
+ status = pcap_set_buffer_size(pc, Bflag);
+ if (status != 0)
+ error("%s: Can't set buffer size: %s",
+ device, pcap_statustostr(status));
+ }
+#ifdef HAVE_PCAP_SET_TSTAMP_TYPE
+ if (jflag != -1) {
+ status = pcap_set_tstamp_type(pc, jflag);
+ if (status < 0)
+ error("%s: Can't set time stamp type: %s",
+ device, pcap_statustostr(status));
+ else if (status > 0)
+ warning("When trying to set timestamp type '%s' on %s: %s",
+ pcap_tstamp_type_val_to_name(jflag), device,
+ pcap_statustostr(status));
+ }
+#endif
+ status = pcap_activate(pc);
+ if (status < 0) {
+ /*
+ * pcap_activate() failed.
+ */
+ cp = pcap_geterr(pc);
+ if (status == PCAP_ERROR)
+ error("%s", cp);
+ else if (status == PCAP_ERROR_NO_SUCH_DEVICE) {
+ /*
+ * Return an error for our caller to handle.
+ */
+ snprintf(ebuf, PCAP_ERRBUF_SIZE, "%s: %s\n(%s)",
+ device, pcap_statustostr(status), cp);
+ } else if (status == PCAP_ERROR_PERM_DENIED && *cp != '\0')
+ error("%s: %s\n(%s)", device,
+ pcap_statustostr(status), cp);
+#ifdef PCAP_ERROR_CAPTURE_NOTSUP
+ else if (status == PCAP_ERROR_CAPTURE_NOTSUP && *cp != '\0')
+ error("%s: %s\n(%s)", device,
+ pcap_statustostr(status), cp);
+#endif
+#ifdef __FreeBSD__
+ else if (status == PCAP_ERROR_RFMON_NOTSUP &&
+ strncmp(device, "wlan", 4) == 0) {
+ char parent[8], newdev[8];
+ char sysctl[32];
+ size_t s = sizeof(parent);
+
+ snprintf(sysctl, sizeof(sysctl),
+ "net.wlan.%d.%%parent", atoi(device + 4));
+ sysctlbyname(sysctl, parent, &s, NULL, 0);
+ strlcpy(newdev, device, sizeof(newdev));
+ /* Suggest a new wlan device. */
+ /* FIXME: incrementing the index this way is not going to work well
+ * when the index is 9 or greater but the only consequence in this
+ * specific case would be an error message that looks a bit odd.
+ */
+ newdev[strlen(newdev)-1]++;
+ error("%s is not a monitor mode VAP\n"
+ "To create a new monitor mode VAP use:\n"
+ " ifconfig %s create wlandev %s wlanmode monitor\n"
+ "and use %s as the tcpdump interface",
+ device, newdev, parent, newdev);
+ }
+#endif
+ else
+ error("%s: %s", device,
+ pcap_statustostr(status));
+ pcap_close(pc);
+ return (NULL);
+ } else if (status > 0) {
+ /*
+ * pcap_activate() succeeded, but it's warning us
+ * of a problem it had.
+ */
+ cp = pcap_geterr(pc);
+ if (status == PCAP_WARNING)
+ warning("%s", cp);
+ else if (status == PCAP_WARNING_PROMISC_NOTSUP &&
+ *cp != '\0')
+ warning("%s: %s\n(%s)", device,
+ pcap_statustostr(status), cp);
+ else
+ warning("%s: %s", device,
+ pcap_statustostr(status));
+ }
+#ifdef HAVE_PCAP_SETDIRECTION
+ if (Qflag != -1) {
+ status = pcap_setdirection(pc, Qflag);
+ if (status != 0)
+ error("%s: pcap_setdirection() failed: %s",
+ device, pcap_geterr(pc));
+ }
+#endif /* HAVE_PCAP_SETDIRECTION */
+#else /* HAVE_PCAP_CREATE */
+ *ebuf = '\0';
+ /*
+ * If no snapshot length was specified, or a length of 0 was
+ * specified, default to 256KB.
+ */
+ if (ndo->ndo_snaplen == 0)
+ ndo->ndo_snaplen = MAXIMUM_SNAPLEN;
+ pc = pcap_open_live(device, ndo->ndo_snaplen, !pflag, timeout, ebuf);
+ if (pc == NULL) {
+ /*
+ * If this failed with "No such device", that means
+ * the interface doesn't exist; return NULL, so that
+ * the caller can see whether the device name is
+ * actually an interface index.
+ */
+ if (strstr(ebuf, "No such device") != NULL)
+ return (NULL);
+ error("%s", ebuf);
+ }
+ if (*ebuf)
+ warning("%s", ebuf);
+#endif /* HAVE_PCAP_CREATE */
+
+ return (pc);
+}
+
+int
+main(int argc, char **argv)
+{
+ int cnt, op, i;
+ bpf_u_int32 localnet = 0, netmask = 0;
+ char *cp, *infile, *cmdbuf, *device, *RFileName, *VFileName, *WFileName;
+ char *endp;
+ pcap_handler callback;
+ int dlt;
+ const char *dlt_name;
+ struct bpf_program fcode;
+#ifndef _WIN32
+ void (*oldhandler)(int);
+#endif
+ struct dump_info dumpinfo;
+ u_char *pcap_userdata;
+ char ebuf[PCAP_ERRBUF_SIZE];
+ char VFileLine[PATH_MAX + 1];
+ const char *username = NULL;
+#ifndef _WIN32
+ const char *chroot_dir = NULL;
+#endif
+ char *ret = NULL;
+ char *end;
+#ifdef HAVE_PCAP_FINDALLDEVS
+ pcap_if_t *devlist;
+ long devnum;
+#endif
+ int status;
+ FILE *VFile;
+#ifdef HAVE_CAPSICUM
+ cap_rights_t rights;
+ int cansandbox;
+#endif /* HAVE_CAPSICUM */
+ int Oflag = 1; /* run filter code optimizer */
+ int yflag_dlt = -1;
+ const char *yflag_dlt_name = NULL;
+ int print = 0;
+
+ netdissect_options Ndo;
+ netdissect_options *ndo = &Ndo;
+
+#ifdef _WIN32
+ /*
+ * We need to look for wpcap.dll in \Windows\System32\Npcap first,
+ * as either:
+ *
+ * 1) WinPcap isn't installed and Npcap isn't installed in "WinPcap
+ * API-compatible Mode", so there's no wpcap.dll in
+ * \Windows\System32, only in \Windows\System32\Npcap;
+ *
+ * 2) WinPcap is installed and Npcap isn't installed in "WinPcap
+ * API-compatible Mode", so the wpcap.dll in \Windows\System32
+ * is a WinPcap DLL, but we'd prefer an Npcap DLL (we should
+ * work with either one if we're configured against WinPcap,
+ * and we'll probably require Npcap if we're configured againt
+ * it), and that's in \Windows\System32\Npcap;
+ *
+ * 3) Npcap is installed in "WinPcap API-compatible Mode", so both
+ * \Windows\System32 and \Windows\System32\Npcap have an Npcap
+ * wpcap.dll.
+ *
+ * Unfortunately, Windows has no notion of an rpath, so we can't
+ * set the rpath to include \Windows\System32\Npcap at link time;
+ * what we need to do is to link wpcap as a delay-load DLL and
+ * add \Windows\System32\Npcap to the DLL search path early in
+ * main() with a call to SetDllDirectory().
+ *
+ * The same applies to packet.dll.
+ *
+ * We add \Windows\System32\Npcap here.
+ *
+ * See https://npcap.com/guide/npcap-devguide.html#npcap-feature-native-dll-implicitly
+ */
+ WCHAR *dll_directory = NULL;
+ size_t dll_directory_buf_len = 0; /* units of bytes */
+ UINT system_directory_buf_len = 0; /* units of WCHARs */
+ UINT system_directory_len; /* units of WCHARs */
+ static const WCHAR npcap[] = L"\\Npcap";
+
+ /*
+ * Get the system directory path, in UTF-16, into a buffer that's
+ * large enough for that directory path plus "\Npcap".
+ *
+ * String manipulation in C, plus fetching a variable-length
+ * string into a buffer whose size is fixed at the time of
+ * the call, with an oddball return value (see below), is just
+ * a huge bag of fun.
+ *
+ * And it's even more fun when dealing with UTF-16, so that the
+ * buffer sizes used in GetSystemDirectoryW() are in different
+ * units from the buffer sizes used in realloc()! We maintain
+ * all sizes/length in units of bytes, not WCHARs, so that our
+ * heads don't explode.
+ */
+ for (;;) {
+ /*
+ * Try to fetch the system directory.
+ *
+ * GetSystemDirectoryW() expects a buffer size in units
+ * of WCHARs, not bytes, and returns a directory path
+ * length in units of WCHARs, not bytes.
+ *
+ * For extra fun, if GetSystemDirectoryW() succeeds,
+ * the return value is the length of the directory
+ * path in units of WCHARs, *not* including the
+ * terminating '\0', but if it fails because the
+ * path string wouldn't fit, the return value is
+ * the length of the directory path in units of WCHARs,
+ * *including* the terminating '\0'.
+ */
+ system_directory_len = GetSystemDirectoryW(dll_directory,
+ system_directory_buf_len);
+ if (system_directory_len == 0)
+ error("GetSystemDirectoryW() failed");
+
+ /*
+ * Did the directory path fit in the buffer?
+ *
+ * As per the above, this means that the return value
+ * *plus 1*, so that the terminating '\0' is counted,
+ * is <= the buffer size.
+ *
+ * (If the directory path, complete with the terminating
+ * '\0', fits *exactly*, the return value would be the
+ * size of the buffer minus 1, as it doesn't count the
+ * terminating '\0', so the test below would succeed.
+ *
+ * If everything *but* the terminating '\0' fits,
+ * the return value would be the size of the buffer + 1,
+ * i.e., the size that the string in question would
+ * have required.
+ *
+ * The astute reader will note that returning the
+ * size of the buffer is not one of the two cases
+ * above, and should never happen.)
+ */
+ if ((system_directory_len + 1) <= system_directory_buf_len) {
+ /*
+ * No. We have a buffer that's large enough
+ * for our purposes.
+ */
+ break;
+ }