pflogd.c revision 1.4 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