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