1/* $OpenBSD: pflogd.c,v 1.66 2023/11/17 12:10:23 claudio Exp $ */ 2 3/* 4 * Copyright (c) 2001 Theo de Raadt 5 * Copyright (c) 2001 Can Erkin Acar 6 * All rights reserved. 7 * 8 * Redistribution and use in source and binary forms, with or without 9 * modification, are permitted provided that the following conditions 10 * are met: 11 * 12 * - Redistributions of source code must retain the above copyright 13 * notice, this list of conditions and the following disclaimer. 14 * - Redistributions in binary form must reproduce the above 15 * copyright notice, this list of conditions and the following 16 * disclaimer in the documentation and/or other materials provided 17 * with the distribution. 18 * 19 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 20 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 21 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS 22 * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE 23 * COPYRIGHT HOLDERS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, 24 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, 25 * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 26 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 27 * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 28 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN 29 * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 30 * POSSIBILITY OF SUCH DAMAGE. 31 */ 32 33#include <sys/types.h> 34#include <sys/ioctl.h> 35#include <sys/stat.h> 36#include <sys/socket.h> 37#include <net/if.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 <err.h> 47#include <errno.h> 48#include <stdarg.h> 49#include <fcntl.h> 50#include <util.h> 51#include "pflogd.h" 52 53pcap_t *hpcap; 54static FILE *dpcap; 55 56int Debug = 0; 57static int snaplen = DEF_SNAPLEN; 58static int cur_snaplen = DEF_SNAPLEN; 59 60volatile sig_atomic_t gotsig_close, gotsig_alrm, gotsig_hup; 61 62char *filename = PFLOGD_LOG_FILE; 63char *interface = PFLOGD_DEFAULT_IF; 64char *filter = NULL; 65 66char errbuf[PCAP_ERRBUF_SIZE]; 67 68int log_debug = 0; 69unsigned int delay = FLUSH_DELAY; 70 71char *copy_argv(char * const *); 72void dump_packet(u_char *, const struct pcap_pkthdr *, const u_char *); 73void dump_packet_nobuf(u_char *, const struct pcap_pkthdr *, const u_char *); 74int flush_buffer(FILE *); 75int if_exists(char *); 76pcap_t *pflog_read_live(const char *, int, int, int, char *); 77void logmsg(int, const char *, ...); 78void purge_buffer(void); 79int reset_dump(void); 80int scan_dump(FILE *, off_t); 81int set_snaplen(int); 82void set_suspended(int); 83void sig_alrm(int); 84void sig_close(int); 85void sig_hup(int); 86void usage(void); 87 88/* buffer must always be greater than snaplen */ 89static int bufpkt = 0; /* number of packets in buffer */ 90static int buflen = 0; /* allocated size of buffer */ 91static char *buffer = NULL; /* packet buffer */ 92static char *bufpos = NULL; /* position in buffer */ 93static int bufleft = 0; /* bytes left in buffer */ 94 95/* if error, stop logging but count dropped packets */ 96static int suspended = -1; 97static long packets_dropped = 0; 98 99void 100set_suspended(int s) 101{ 102 if (suspended == s) 103 return; 104 105 suspended = s; 106 setproctitle("[%s] -s %d -i %s -f %s", 107 suspended ? "suspended" : "running", 108 cur_snaplen, interface, filename); 109} 110 111char * 112copy_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 137void 138logmsg(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__dead void 152usage(void) 153{ 154 fprintf(stderr, "usage: pflogd [-Dx] [-d delay] [-f filename]"); 155 fprintf(stderr, " [-i interface] [-s snaplen]\n"); 156 fprintf(stderr, " [expression]\n"); 157 exit(1); 158} 159 160void 161sig_close(int sig) 162{ 163 pcap_breakloop(hpcap); 164 gotsig_close = 1; 165} 166 167void 168sig_hup(int sig) 169{ 170 pcap_breakloop(hpcap); 171 gotsig_hup = 1; 172} 173 174void 175sig_alrm(int sig) 176{ 177 pcap_breakloop(hpcap); 178 gotsig_alrm = 1; 179} 180 181void 182set_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 195int 196if_exists(char *ifname) 197{ 198 return (if_nametoindex(ifname) != 0); 199} 200 201pcap_t * 202pflog_read_live(const char *source, int slen, int promisc, int to_ms, 203 char *ebuf) 204{ 205 int fd; 206 struct bpf_version bv; 207 struct ifreq ifr; 208 u_int v, dlt = DLT_PFLOG; 209 pcap_t *p; 210 211 if (source == NULL || slen <= 0) 212 return (NULL); 213 214 p = pcap_create(source, ebuf); 215 if (p == NULL) 216 return (NULL); 217 218 /* Open bpf(4) read only */ 219 if ((fd = open("/dev/bpf", O_RDONLY)) == -1) 220 return (NULL); 221 222 if (ioctl(fd, BIOCVERSION, &bv) == -1) { 223 snprintf(ebuf, PCAP_ERRBUF_SIZE, "BIOCVERSION: %s", 224 pcap_strerror(errno)); 225 goto bad; 226 } 227 228 if (bv.bv_major != BPF_MAJOR_VERSION || 229 bv.bv_minor < BPF_MINOR_VERSION) { 230 snprintf(ebuf, PCAP_ERRBUF_SIZE, 231 "kernel bpf filter out of date"); 232 goto bad; 233 } 234 235 strlcpy(ifr.ifr_name, source, sizeof(ifr.ifr_name)); 236 if (ioctl(fd, BIOCSETIF, &ifr) == -1) { 237 snprintf(ebuf, PCAP_ERRBUF_SIZE, "BIOCSETIF: %s", 238 pcap_strerror(errno)); 239 goto bad; 240 } 241 242 if (dlt != (u_int) -1 && ioctl(fd, BIOCSDLT, &dlt)) { 243 snprintf(ebuf, PCAP_ERRBUF_SIZE, "BIOCSDLT: %s", 244 pcap_strerror(errno)); 245 goto bad; 246 } 247 248 p->fd = fd; 249 p->snapshot = slen; 250 p->linktype = dlt; 251 252 /* set timeout */ 253 if (to_ms != 0) { 254 struct timeval to; 255 to.tv_sec = to_ms / 1000; 256 to.tv_usec = (to_ms * 1000) % 1000000; 257 if (ioctl(p->fd, BIOCSWTIMEOUT, &to) == -1) { 258 snprintf(ebuf, PCAP_ERRBUF_SIZE, "BIOCSWTIMEOUT: %s", 259 pcap_strerror(errno)); 260 goto bad; 261 } 262 } 263 if (promisc) 264 /* this is allowed to fail */ 265 ioctl(fd, BIOCPROMISC, NULL); 266 267 if (ioctl(fd, BIOCGBLEN, &v) == -1) { 268 snprintf(ebuf, PCAP_ERRBUF_SIZE, "BIOCGBLEN: %s", 269 pcap_strerror(errno)); 270 goto bad; 271 } 272 p->bufsize = v; 273 p->buffer = malloc(p->bufsize); 274 if (p->buffer == NULL) { 275 snprintf(ebuf, PCAP_ERRBUF_SIZE, "malloc: %s", 276 pcap_strerror(errno)); 277 goto bad; 278 } 279 p->activated = 1; 280 return (p); 281 282bad: 283 pcap_close(p); 284 return (NULL); 285} 286 287int 288init_pcap(void) 289{ 290 hpcap = pflog_read_live(interface, snaplen, 1, PCAP_TO_MS, errbuf); 291 if (hpcap == NULL) { 292 logmsg(LOG_ERR, "Failed to initialize: %s", errbuf); 293 return (-1); 294 } 295 296 if (pcap_datalink(hpcap) != DLT_PFLOG) { 297 logmsg(LOG_ERR, "Invalid datalink type"); 298 pcap_close(hpcap); 299 hpcap = NULL; 300 return (-1); 301 } 302 303 set_pcap_filter(); 304 305 /* lock */ 306 if (ioctl(pcap_fileno(hpcap), BIOCLOCK) == -1) { 307 logmsg(LOG_ERR, "BIOCLOCK: %s", strerror(errno)); 308 return (-1); 309 } 310 311 return (0); 312} 313 314int 315set_snaplen(int snap) 316{ 317 if (priv_set_snaplen(snap)) 318 return (1); 319 320 if (cur_snaplen > snap) 321 purge_buffer(); 322 323 cur_snaplen = snap; 324 325 return (0); 326} 327 328int 329reset_dump(void) 330{ 331 struct pcap_file_header hdr; 332 struct stat st; 333 int fd; 334 FILE *fp; 335 336 if (hpcap == NULL) 337 return (-1); 338 339 if (dpcap) { 340 flush_buffer(dpcap); 341 fclose(dpcap); 342 dpcap = NULL; 343 } 344 345 /* 346 * Basically reimplement pcap_dump_open() because it truncates 347 * files and duplicates headers and such. 348 */ 349 fd = priv_open_log(); 350 if (fd < 0) 351 return (-1); 352 353 fp = fdopen(fd, "a+"); 354 355 if (fp == NULL) { 356 logmsg(LOG_ERR, "Error: %s: %s", filename, strerror(errno)); 357 close(fd); 358 return (-1); 359 } 360 if (fstat(fileno(fp), &st) == -1) { 361 logmsg(LOG_ERR, "Error: %s: %s", filename, strerror(errno)); 362 fclose(fp); 363 return (-1); 364 } 365 366 /* set FILE unbuffered, we do our own buffering */ 367 if (setvbuf(fp, NULL, _IONBF, 0)) { 368 logmsg(LOG_ERR, "Failed to set output buffers"); 369 fclose(fp); 370 return (-1); 371 } 372 373#define TCPDUMP_MAGIC 0xa1b2c3d4 374 375 if (st.st_size == 0) { 376 if (snaplen != cur_snaplen) { 377 logmsg(LOG_NOTICE, "Using snaplen %d", snaplen); 378 if (set_snaplen(snaplen)) 379 logmsg(LOG_WARNING, 380 "Failed, using old settings"); 381 } 382 hdr.magic = TCPDUMP_MAGIC; 383 hdr.version_major = PCAP_VERSION_MAJOR; 384 hdr.version_minor = PCAP_VERSION_MINOR; 385 hdr.thiszone = hpcap->tzoff; 386 hdr.snaplen = hpcap->snapshot; 387 hdr.sigfigs = 0; 388 hdr.linktype = hpcap->linktype; 389 390 if (fwrite((char *)&hdr, sizeof(hdr), 1, fp) != 1) { 391 fclose(fp); 392 return (-1); 393 } 394 } else if (scan_dump(fp, st.st_size)) { 395 fclose(fp); 396 logmsg(LOG_ERR, 397 "Invalid/incompatible log file, move it away"); 398 return (-1); 399 } 400 401 dpcap = fp; 402 403 set_suspended(0); 404 flush_buffer(fp); 405 406 return (0); 407} 408 409int 410scan_dump(FILE *fp, off_t size) 411{ 412 struct pcap_file_header hdr; 413 struct pcap_pkthdr ph; 414 off_t pos; 415 416 /* 417 * Must read the file, compare the header against our new 418 * options (in particular, snaplen) and adjust our options so 419 * that we generate a correct file. Furthermore, check the file 420 * for consistency so that we can append safely. 421 * 422 * XXX this may take a long time for large logs. 423 */ 424 (void) fseek(fp, 0L, SEEK_SET); 425 426 if (fread((char *)&hdr, sizeof(hdr), 1, fp) != 1) { 427 logmsg(LOG_ERR, "Short file header"); 428 return (1); 429 } 430 431 if (hdr.magic != TCPDUMP_MAGIC || 432 hdr.version_major != PCAP_VERSION_MAJOR || 433 hdr.version_minor != PCAP_VERSION_MINOR || 434 hdr.linktype != hpcap->linktype || 435 hdr.snaplen > PFLOGD_MAXSNAPLEN) { 436 return (1); 437 } 438 439 pos = sizeof(hdr); 440 441 while (!feof(fp)) { 442 off_t len = fread((char *)&ph, 1, sizeof(ph), fp); 443 if (len == 0) 444 break; 445 446 if (len != sizeof(ph)) 447 goto error; 448 if (ph.caplen > hdr.snaplen || ph.caplen > PFLOGD_MAXSNAPLEN) 449 goto error; 450 pos += sizeof(ph) + ph.caplen; 451 if (pos > size) 452 goto error; 453 fseek(fp, ph.caplen, SEEK_CUR); 454 } 455 456 if (pos != size) 457 goto error; 458 459 if (hdr.snaplen != cur_snaplen) { 460 logmsg(LOG_WARNING, 461 "Existing file has different snaplen %u, using it", 462 hdr.snaplen); 463 if (set_snaplen(hdr.snaplen)) { 464 logmsg(LOG_WARNING, 465 "Failed, using old settings, offset %llu", 466 (unsigned long long) size); 467 } 468 } 469 470 return (0); 471 472 error: 473 logmsg(LOG_ERR, "Corrupted log file."); 474 return (1); 475} 476 477/* dump a packet directly to the stream, which is unbuffered */ 478void 479dump_packet_nobuf(u_char *user, const struct pcap_pkthdr *h, const u_char *sp) 480{ 481 FILE *f = (FILE *)user; 482 483 if (suspended) { 484 packets_dropped++; 485 return; 486 } 487 488 if (fwrite((char *)h, sizeof(*h), 1, f) != 1) { 489 off_t pos = ftello(f); 490 491 /* try to undo header to prevent corruption */ 492 if (pos < sizeof(*h) || 493 ftruncate(fileno(f), pos - sizeof(*h))) { 494 logmsg(LOG_ERR, "Write failed, corrupted logfile!"); 495 set_suspended(1); 496 gotsig_close = 1; 497 return; 498 } 499 goto error; 500 } 501 502 if (fwrite((char *)sp, h->caplen, 1, f) != 1) 503 goto error; 504 505 return; 506 507error: 508 set_suspended(1); 509 packets_dropped ++; 510 logmsg(LOG_ERR, "Logging suspended: fwrite: %s", strerror(errno)); 511} 512 513int 514flush_buffer(FILE *f) 515{ 516 off_t offset; 517 int len = bufpos - buffer; 518 519 if (len <= 0) 520 return (0); 521 522 offset = ftello(f); 523 if (offset == (off_t)-1) { 524 set_suspended(1); 525 logmsg(LOG_ERR, "Logging suspended: ftello: %s", 526 strerror(errno)); 527 return (1); 528 } 529 530 if (fwrite(buffer, len, 1, f) != 1) { 531 set_suspended(1); 532 logmsg(LOG_ERR, "Logging suspended: fwrite: %s", 533 strerror(errno)); 534 ftruncate(fileno(f), offset); 535 return (1); 536 } 537 538 set_suspended(0); 539 bufpos = buffer; 540 bufleft = buflen; 541 bufpkt = 0; 542 543 return (0); 544} 545 546void 547purge_buffer(void) 548{ 549 packets_dropped += bufpkt; 550 551 set_suspended(0); 552 bufpos = buffer; 553 bufleft = buflen; 554 bufpkt = 0; 555} 556 557/* append packet to the buffer, flushing if necessary */ 558void 559dump_packet(u_char *user, const struct pcap_pkthdr *h, const u_char *sp) 560{ 561 FILE *f = (FILE *)user; 562 size_t len = sizeof(*h) + h->caplen; 563 564 if (len < sizeof(*h) || h->caplen > (size_t)cur_snaplen) { 565 logmsg(LOG_NOTICE, "invalid size %zu (%d/%d), packet dropped", 566 len, cur_snaplen, snaplen); 567 packets_dropped++; 568 return; 569 } 570 571 if (len <= bufleft) 572 goto append; 573 574 if (suspended) { 575 packets_dropped++; 576 return; 577 } 578 579 if (flush_buffer(f)) { 580 packets_dropped++; 581 return; 582 } 583 584 if (len > bufleft) { 585 dump_packet_nobuf(user, h, sp); 586 return; 587 } 588 589 append: 590 memcpy(bufpos, h, sizeof(*h)); 591 memcpy(bufpos + sizeof(*h), sp, h->caplen); 592 593 bufpos += len; 594 bufleft -= len; 595 bufpkt++; 596 597 return; 598} 599 600int 601main(int argc, char **argv) 602{ 603 int ch, np, ret, Pflag = 0, Xflag = 0; 604 pcap_handler phandler = dump_packet; 605 const char *errstr = NULL; 606 607 ret = 0; 608 609 while ((ch = getopt(argc, argv, "Dxd:f:i:Ps:")) != -1) { 610 switch (ch) { 611 case 'D': 612 Debug = 1; 613 break; 614 case 'd': 615 delay = strtonum(optarg, 5, 60*60, &errstr); 616 if (errstr) 617 usage(); 618 break; 619 case 'f': 620 filename = optarg; 621 break; 622 case 'i': 623 interface = optarg; 624 break; 625 case 'P': /* used internally, exec the child */ 626 if (strcmp("-P", argv[1]) == 0) 627 Pflag = 1; 628 break; 629 case 's': 630 snaplen = strtonum(optarg, 0, PFLOGD_MAXSNAPLEN, 631 &errstr); 632 if (snaplen <= 0) 633 snaplen = DEF_SNAPLEN; 634 if (errstr) 635 snaplen = PFLOGD_MAXSNAPLEN; 636 cur_snaplen = snaplen; 637 break; 638 case 'x': 639 Xflag = 1; 640 break; 641 default: 642 usage(); 643 } 644 645 } 646 647 log_debug = Debug; 648 argc -= optind; 649 argv += optind; 650 651 /* does interface exist */ 652 if (!if_exists(interface)) { 653 warn("Failed to initialize: %s", interface); 654 logmsg(LOG_ERR, "Failed to initialize: %s", interface); 655 logmsg(LOG_ERR, "Exiting, init failure"); 656 exit(1); 657 } 658 659 if (!Debug) { 660 openlog("pflogd", LOG_PID, LOG_DAEMON); 661 if (!Pflag) { 662 if (daemon(0, 0)) { 663 logmsg(LOG_WARNING, 664 "Failed to become daemon: %s", 665 strerror(errno)); 666 } 667 } 668 } 669 670 tzset(); 671 (void)umask(S_IRWXG | S_IRWXO); 672 673 /* filter will be used by the privileged process */ 674 if (argc) { 675 filter = copy_argv(argv); 676 if (filter == NULL) 677 logmsg(LOG_NOTICE, "Failed to form filter expression"); 678 } 679 argc += optind; 680 argv -= optind; 681 682 /* Privilege separation begins here */ 683 priv_init(Pflag, argc, argv); 684 685 if (pledge("stdio recvfd", NULL) == -1) 686 err(1, "pledge"); 687 688 setproctitle("[initializing]"); 689 /* Process is now unprivileged and inside a chroot */ 690 signal(SIGTERM, sig_close); 691 siginterrupt(SIGTERM, 1); 692 signal(SIGINT, sig_close); 693 siginterrupt(SIGINT, 1); 694 signal(SIGQUIT, sig_close); 695 siginterrupt(SIGQUIT, 1); 696 signal(SIGALRM, sig_alrm); 697 siginterrupt(SIGALRM, 1); 698 signal(SIGHUP, sig_hup); 699 siginterrupt(SIGHUP, 1); 700 alarm(delay); 701 702 if (priv_init_pcap(snaplen)) 703 errx(1, "priv_init_pcap failed"); 704 705 buffer = malloc(PFLOGD_BUFSIZE); 706 707 if (buffer == NULL) { 708 logmsg(LOG_WARNING, "Failed to allocate output buffer"); 709 phandler = dump_packet_nobuf; 710 } else { 711 bufleft = buflen = PFLOGD_BUFSIZE; 712 bufpos = buffer; 713 bufpkt = 0; 714 } 715 716 if (reset_dump() < 0) { 717 if (Xflag) 718 return (1); 719 720 logmsg(LOG_ERR, "Logging suspended: open error"); 721 set_suspended(1); 722 } else if (Xflag) 723 return (0); 724 725 while (1) { 726 np = pcap_dispatch(hpcap, PCAP_NUM_PKTS, 727 phandler, (u_char *)dpcap); 728 if (np == -1) { 729 if (!if_exists(interface)) { 730 logmsg(LOG_NOTICE, "interface %s went away", 731 interface); 732 ret = -1; 733 break; 734 } 735 logmsg(LOG_NOTICE, "%s", pcap_geterr(hpcap)); 736 } 737 738 if (gotsig_close) 739 break; 740 if (gotsig_hup) { 741 int was_suspended = suspended; 742 if (reset_dump()) { 743 logmsg(LOG_ERR, 744 "Logging suspended: open error"); 745 set_suspended(1); 746 } else { 747 if (was_suspended) 748 logmsg(LOG_NOTICE, "Logging resumed"); 749 } 750 gotsig_hup = 0; 751 } 752 753 if (gotsig_alrm) { 754 if (dpcap) 755 flush_buffer(dpcap); 756 else 757 gotsig_hup = 1; 758 gotsig_alrm = 0; 759 alarm(delay); 760 } 761 } 762 763 logmsg(LOG_NOTICE, "Exiting"); 764 if (dpcap) { 765 flush_buffer(dpcap); 766 fclose(dpcap); 767 } 768 purge_buffer(); 769 770 pcap_close(hpcap); 771 if (!Debug) 772 closelog(); 773 return (ret); 774} 775