pflogd.c revision 1.2 1 /* $OpenBSD: pflogd.c,v 1.30 2004/08/08 19:04:25 deraadt 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 #ifdef __OpenBSD__
209 if (ioctl(pcap_fileno(hpcap), BIOCLOCK) < 0) {
210 logmsg(LOG_ERR, "BIOCLOCK: %s", strerror(errno));
211 return (-1);
212 }
213 #endif
214
215 return (0);
216 }
217
218 int
219 set_snaplen(int snap)
220 {
221 if (priv_set_snaplen(snap))
222 return (1);
223
224 if (cur_snaplen > snap)
225 purge_buffer();
226
227 cur_snaplen = snap;
228
229 return (0);
230 }
231
232 int
233 reset_dump(void)
234 {
235 struct pcap_file_header hdr;
236 struct stat st;
237 int fd;
238 FILE *fp;
239
240 if (hpcap == NULL)
241 return (-1);
242
243 if (dpcap) {
244 flush_buffer(dpcap);
245 fclose(dpcap);
246 dpcap = NULL;
247 }
248
249 /*
250 * Basically reimplement pcap_dump_open() because it truncates
251 * files and duplicates headers and such.
252 */
253 fd = priv_open_log();
254 if (fd < 0)
255 return (1);
256
257 fp = fdopen(fd, "a+");
258
259 if (fp == NULL) {
260 close(fd);
261 logmsg(LOG_ERR, "Error: %s: %s", filename, strerror(errno));
262 return (1);
263 }
264 if (fstat(fileno(fp), &st) == -1) {
265 fclose(fp);
266 logmsg(LOG_ERR, "Error: %s: %s", filename, strerror(errno));
267 return (1);
268 }
269
270 /* set FILE unbuffered, we do our own buffering */
271 if (setvbuf(fp, NULL, _IONBF, 0)) {
272 fclose(fp);
273 logmsg(LOG_ERR, "Failed to set output buffers");
274 return (1);
275 }
276
277 #define TCPDUMP_MAGIC 0xa1b2c3d4
278
279 if (st.st_size == 0) {
280 if (snaplen != cur_snaplen) {
281 logmsg(LOG_NOTICE, "Using snaplen %d", snaplen);
282 if (set_snaplen(snaplen)) {
283 fclose(fp);
284 logmsg(LOG_WARNING,
285 "Failed, using old settings");
286 }
287 }
288 hdr.magic = TCPDUMP_MAGIC;
289 hdr.version_major = PCAP_VERSION_MAJOR;
290 hdr.version_minor = PCAP_VERSION_MINOR;
291 hdr.thiszone = hpcap->tzoff;
292 hdr.snaplen = hpcap->snapshot;
293 hdr.sigfigs = 0;
294 hdr.linktype = hpcap->linktype;
295
296 if (fwrite((char *)&hdr, sizeof(hdr), 1, fp) != 1) {
297 fclose(fp);
298 return (1);
299 }
300 } else if (scan_dump(fp, st.st_size)) {
301 /* XXX move file and continue? */
302 fclose(fp);
303 return (1);
304 }
305
306 dpcap = fp;
307
308 set_suspended(0);
309 flush_buffer(fp);
310
311 return (0);
312 }
313
314 int
315 scan_dump(FILE *fp, off_t size)
316 {
317 struct pcap_file_header hdr;
318 #ifdef __OpenBSD__
319 struct pcap_pkthdr ph;
320 #else
321 struct pcap_sf_pkthdr ph;
322 #endif
323 off_t pos;
324
325 /*
326 * Must read the file, compare the header against our new
327 * options (in particular, snaplen) and adjust our options so
328 * that we generate a correct file. Furthermore, check the file
329 * for consistency so that we can append safely.
330 *
331 * XXX this may take a long time for large logs.
332 */
333 (void) fseek(fp, 0L, SEEK_SET);
334
335 if (fread((char *)&hdr, sizeof(hdr), 1, fp) != 1) {
336 logmsg(LOG_ERR, "Short file header");
337 return (1);
338 }
339
340 if (hdr.magic != TCPDUMP_MAGIC ||
341 hdr.version_major != PCAP_VERSION_MAJOR ||
342 hdr.version_minor != PCAP_VERSION_MINOR ||
343 hdr.linktype != hpcap->linktype ||
344 hdr.snaplen > PFLOGD_MAXSNAPLEN) {
345 logmsg(LOG_ERR, "Invalid/incompatible log file, move it away");
346 return (1);
347 }
348
349 pos = sizeof(hdr);
350
351 while (!feof(fp)) {
352 off_t len = fread((char *)&ph, 1, sizeof(ph), fp);
353 if (len == 0)
354 break;
355
356 if (len != sizeof(ph))
357 goto error;
358 if (ph.caplen > hdr.snaplen || ph.caplen > PFLOGD_MAXSNAPLEN)
359 goto error;
360 pos += sizeof(ph) + ph.caplen;
361 if (pos > size)
362 goto error;
363 fseek(fp, ph.caplen, SEEK_CUR);
364 }
365
366 if (pos != size)
367 goto error;
368
369 if (hdr.snaplen != cur_snaplen) {
370 logmsg(LOG_WARNING,
371 "Existing file has different snaplen %u, using it",
372 hdr.snaplen);
373 if (set_snaplen(hdr.snaplen)) {
374 logmsg(LOG_WARNING,
375 "Failed, using old settings, offset %llu",
376 (unsigned long long) size);
377 }
378 }
379
380 return (0);
381
382 error:
383 logmsg(LOG_ERR, "Corrupted log file.");
384 return (1);
385 }
386
387 /* dump a packet directly to the stream, which is unbuffered */
388 void
389 dump_packet_nobuf(u_char *user, const struct pcap_pkthdr *h, const u_char *sp)
390 {
391 #ifndef __OpenBSD__
392 struct pcap_sf_pkthdr sf_hdr;
393 #endif
394 FILE *f = (FILE *)user;
395
396 if (suspended) {
397 packets_dropped++;
398 return;
399 }
400
401 #ifndef __OpenBSD__
402 sf_hdr.ts.tv_sec = h->ts.tv_sec;
403 sf_hdr.ts.tv_usec = h->ts.tv_usec;
404 sf_hdr.caplen = h->caplen;
405 sf_hdr.len = h->len;
406 #endif
407
408 #ifdef __OpenBSD__
409 if (fwrite((char *)h, sizeof(*h), 1, f) != 1) {
410 #else
411 if (fwrite(&sf_hdr, sizeof(sf_hdr), 1, f) != 1) {
412 #endif
413 /* try to undo header to prevent corruption */
414 off_t pos = ftello(f);
415 #ifdef __OpenBSD__
416 if (pos < sizeof(*h) ||
417 ftruncate(fileno(f), pos - sizeof(*h))) {
418 #else
419 if (pos < sizeof(sf_hdr) ||
420 ftruncate(fileno(f), pos - sizeof(sf_hdr))) {
421 #endif
422 logmsg(LOG_ERR, "Write failed, corrupted logfile!");
423 set_suspended(1);
424 gotsig_close = 1;
425 return;
426 }
427 goto error;
428 }
429
430 if (fwrite((char *)sp, h->caplen, 1, f) != 1)
431 goto error;
432
433 return;
434
435 error:
436 set_suspended(1);
437 packets_dropped ++;
438 logmsg(LOG_ERR, "Logging suspended: fwrite: %s", strerror(errno));
439 }
440
441 int
442 flush_buffer(FILE *f)
443 {
444 off_t offset;
445 int len = bufpos - buffer;
446
447 if (len <= 0)
448 return (0);
449
450 offset = ftello(f);
451 if (offset == (off_t)-1) {
452 set_suspended(1);
453 logmsg(LOG_ERR, "Logging suspended: ftello: %s",
454 strerror(errno));
455 return (1);
456 }
457
458 if (fwrite(buffer, len, 1, f) != 1) {
459 set_suspended(1);
460 logmsg(LOG_ERR, "Logging suspended: fwrite: %s",
461 strerror(errno));
462 ftruncate(fileno(f), offset);
463 return (1);
464 }
465
466 set_suspended(0);
467 bufpos = buffer;
468 bufleft = buflen;
469 bufpkt = 0;
470
471 return (0);
472 }
473
474 void
475 purge_buffer(void)
476 {
477 packets_dropped += bufpkt;
478
479 set_suspended(0);
480 bufpos = buffer;
481 bufleft = buflen;
482 bufpkt = 0;
483 }
484
485 /* append packet to the buffer, flushing if necessary */
486 void
487 dump_packet(u_char *user, const struct pcap_pkthdr *h, const u_char *sp)
488 {
489 FILE *f = (FILE *)user;
490 #ifdef __OpenBSD__
491 size_t len = sizeof(*h) + h->caplen;
492 #else
493 struct pcap_sf_pkthdr sf_hdr;
494 size_t len = sizeof(sf_hdr) + h->caplen;
495 #endif
496
497 if (len < sizeof(*h) || h->caplen > (size_t)cur_snaplen) {
498 logmsg(LOG_NOTICE, "invalid size %u (%u/%u), packet dropped",
499 len, cur_snaplen, snaplen);
500 packets_dropped++;
501 return;
502 }
503
504 if (len <= bufleft)
505 goto append;
506
507 if (suspended) {
508 packets_dropped++;
509 return;
510 }
511
512 if (flush_buffer(f)) {
513 packets_dropped++;
514 return;
515 }
516
517 if (len > bufleft) {
518 dump_packet_nobuf(user, h, sp);
519 return;
520 }
521
522 append:
523 #ifdef __OpenBSD__
524 memcpy(bufpos, h, sizeof(*h));
525 memcpy(bufpos + sizeof(*h), sp, h->caplen);
526 #else
527 sf_hdr.ts.tv_sec = h->ts.tv_sec;
528 sf_hdr.ts.tv_usec = h->ts.tv_usec;
529 sf_hdr.caplen = h->caplen;
530 sf_hdr.len = h->len;
531
532 memcpy(bufpos, &sf_hdr, sizeof(sf_hdr));
533 memcpy(bufpos + sizeof(sf_hdr), sp, h->caplen);
534 #endif
535
536 bufpos += len;
537 bufleft -= len;
538 bufpkt++;
539
540 return;
541 }
542
543 int
544 main(int argc, char **argv)
545 {
546 struct pcap_stat pstat;
547 int ch, np, Xflag = 0;
548 pcap_handler phandler = dump_packet;
549 const char *errstr = NULL;
550
551 closefrom(STDERR_FILENO + 1);
552
553 while ((ch = getopt(argc, argv, "Dxd:s:f:")) != -1) {
554 switch (ch) {
555 case 'D':
556 Debug = 1;
557 break;
558 case 'd':
559 delay = strtonum(optarg, 5, 60*60, &errstr);
560 if (errstr)
561 usage();
562 break;
563 case 'f':
564 filename = optarg;
565 break;
566 case 's':
567 snaplen = strtonum(optarg, 0, PFLOGD_MAXSNAPLEN,
568 &errstr);
569 if (snaplen <= 0)
570 snaplen = DEF_SNAPLEN;
571 if (errstr)
572 snaplen = PFLOGD_MAXSNAPLEN;
573 break;
574 case 'x':
575 Xflag++;
576 break;
577 default:
578 usage();
579 }
580
581 }
582
583 log_debug = Debug;
584 argc -= optind;
585 argv += optind;
586
587 if (!Debug) {
588 openlog("pflogd", LOG_PID | LOG_CONS, LOG_DAEMON);
589 if (daemon(0, 0)) {
590 logmsg(LOG_WARNING, "Failed to become daemon: %s",
591 strerror(errno));
592 }
593 pidfile(NULL);
594 }
595
596 (void)umask(S_IRWXG | S_IRWXO);
597
598 /* filter will be used by the privileged process */
599 if (argc) {
600 filter = copy_argv(argv);
601 if (filter == NULL)
602 logmsg(LOG_NOTICE, "Failed to form filter expression");
603 }
604
605 /* initialize pcap before dropping privileges */
606 if (init_pcap()) {
607 logmsg(LOG_ERR, "Exiting, init failure");
608 exit(1);
609 }
610
611 /* Privilege separation begins here */
612 if (priv_init()) {
613 logmsg(LOG_ERR, "unable to privsep");
614 exit(1);
615 }
616
617 setproctitle("[initializing]");
618 /* Process is now unprivileged and inside a chroot */
619 signal(SIGTERM, sig_close);
620 signal(SIGINT, sig_close);
621 signal(SIGQUIT, sig_close);
622 signal(SIGALRM, sig_alrm);
623 signal(SIGHUP, sig_hup);
624 alarm(delay);
625
626 buffer = malloc(PFLOGD_BUFSIZE);
627
628 if (buffer == NULL) {
629 logmsg(LOG_WARNING, "Failed to allocate output buffer");
630 phandler = dump_packet_nobuf;
631 } else {
632 bufleft = buflen = PFLOGD_BUFSIZE;
633 bufpos = buffer;
634 bufpkt = 0;
635 }
636
637 if (reset_dump()) {
638 if (Xflag)
639 return (1);
640
641 logmsg(LOG_ERR, "Logging suspended: open error");
642 set_suspended(1);
643 } else if (Xflag)
644 return (0);
645
646 while (1) {
647 np = pcap_dispatch(hpcap, PCAP_NUM_PKTS,
648 dump_packet, (u_char *)dpcap);
649 if (np < 0)
650 logmsg(LOG_NOTICE, "%s", pcap_geterr(hpcap));
651
652 if (gotsig_close)
653 break;
654 if (gotsig_hup) {
655 if (reset_dump()) {
656 logmsg(LOG_ERR,
657 "Logging suspended: open error");
658 set_suspended(1);
659 }
660 gotsig_hup = 0;
661 }
662
663 if (gotsig_alrm) {
664 if (dpcap)
665 flush_buffer(dpcap);
666 gotsig_alrm = 0;
667 alarm(delay);
668 }
669 }
670
671 logmsg(LOG_NOTICE, "Exiting");
672 if (dpcap) {
673 flush_buffer(dpcap);
674 fclose(dpcap);
675 }
676 purge_buffer();
677
678 if (pcap_stats(hpcap, &pstat) < 0)
679 logmsg(LOG_WARNING, "Reading stats: %s", pcap_geterr(hpcap));
680 else
681 logmsg(LOG_NOTICE,
682 "%u packets received, %u/%u dropped (kernel/pflogd)",
683 pstat.ps_recv, pstat.ps_drop, packets_dropped);
684
685 pcap_close(hpcap);
686 if (!Debug)
687 closelog();
688 return (0);
689 }
690