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