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