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