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