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