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