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