xref: /freebsd/contrib/pf/pflogd/pflogd.c (revision 2357939bc239bd5334a169b62313806178dd8f30)
1 /*	$OpenBSD: pflogd.c,v 1.21 2003/08/22 21:50:34 david 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/cdefs.h>
34 __FBSDID("$FreeBSD$");
35 
36 #include <sys/types.h>
37 #include <sys/file.h>
38 #include <sys/stat.h>
39 #include <stdio.h>
40 #include <stdlib.h>
41 #include <string.h>
42 #include <unistd.h>
43 #include <pcap-int.h>
44 #include <pcap.h>
45 #include <syslog.h>
46 #include <signal.h>
47 #include <errno.h>
48 #include <stdarg.h>
49 #include <fcntl.h>
50 #ifdef __FreeBSD__
51 #include "pidfile.h"
52 #else
53 #include <util.h>
54 #endif
55 
56 #define DEF_SNAPLEN 116		/* default plus allow for larger header of pflog */
57 #define PCAP_TO_MS 500		/* pcap read timeout (ms) */
58 #define PCAP_NUM_PKTS 1000	/* max number of packets to process at each loop */
59 #define PCAP_OPT_FIL 0		/* filter optimization */
60 #define FLUSH_DELAY 60		/* flush delay */
61 
62 #define PFLOGD_LOG_FILE		"/var/log/pflog"
63 #define PFLOGD_DEFAULT_IF	"pflog0"
64 
65 pcap_t *hpcap;
66 pcap_dumper_t *dpcap;
67 
68 int Debug = 0;
69 int snaplen = DEF_SNAPLEN;
70 
71 volatile sig_atomic_t gotsig_close, gotsig_alrm, gotsig_hup;
72 
73 char *filename = PFLOGD_LOG_FILE;
74 char *interface = PFLOGD_DEFAULT_IF;
75 char *filter = NULL;
76 
77 char errbuf[PCAP_ERRBUF_SIZE];
78 
79 int log_debug = 0;
80 unsigned int delay = FLUSH_DELAY;
81 
82 char *copy_argv(char * const *argv);
83 int   init_pcap(void);
84 void  logmsg(int priority, const char *message, ...);
85 int   reset_dump(void);
86 void  sig_alrm(int);
87 void  sig_close(int);
88 void  sig_hup(int);
89 void  usage(void);
90 
91 
92 char *
93 copy_argv(char * const *argv)
94 {
95 	size_t len = 0, n;
96 	char *buf;
97 
98 	if (argv == NULL)
99 		return (NULL);
100 
101 	for (n = 0; argv[n]; n++)
102 		len += strlen(argv[n])+1;
103 	if (len == 0)
104 		return (NULL);
105 
106 	buf = malloc(len);
107 	if (buf == NULL)
108 		return (NULL);
109 
110 	strlcpy(buf, argv[0], len);
111 	for (n = 1; argv[n]; n++) {
112 		strlcat(buf, " ", len);
113 		strlcat(buf, argv[n], len);
114 	}
115 	return (buf);
116 }
117 
118 void
119 logmsg(int pri, const char *message, ...)
120 {
121 	va_list ap;
122 	va_start(ap, message);
123 
124 	if (log_debug) {
125 		vfprintf(stderr, message, ap);
126 		fprintf(stderr, "\n");
127 	} else
128 		vsyslog(pri, message, ap);
129 	va_end(ap);
130 }
131 
132 #ifdef __FreeBSD__
133 __dead2 void
134 #else
135 __dead void
136 #endif
137 usage(void)
138 {
139 	fprintf(stderr, "usage: pflogd [-D] [-d delay] [-f filename] ");
140 	fprintf(stderr, "[-s snaplen] [expression]\n");
141 	exit(1);
142 }
143 
144 void
145 sig_close(int sig)
146 {
147 	gotsig_close = 1;
148 }
149 
150 void
151 sig_hup(int sig)
152 {
153 	gotsig_hup = 1;
154 }
155 
156 void
157 sig_alrm(int sig)
158 {
159 	gotsig_alrm = 1;
160 }
161 
162 int
163 init_pcap(void)
164 {
165 	struct bpf_program bprog;
166 	pcap_t *oldhpcap = hpcap;
167 
168 	hpcap = pcap_open_live(interface, snaplen, 1, PCAP_TO_MS, errbuf);
169 	if (hpcap == NULL) {
170 		logmsg(LOG_ERR, "Failed to initialize: %s", errbuf);
171 		hpcap = oldhpcap;
172 		return (-1);
173 	}
174 
175 	if (pcap_compile(hpcap, &bprog, filter, PCAP_OPT_FIL, 0) < 0)
176 		logmsg(LOG_WARNING, "%s", pcap_geterr(hpcap));
177 	else if (pcap_setfilter(hpcap, &bprog) < 0)
178 		logmsg(LOG_WARNING, "%s", pcap_geterr(hpcap));
179 	if (filter != NULL)
180 		free(filter);
181 
182 	if (pcap_datalink(hpcap) != DLT_PFLOG) {
183 		logmsg(LOG_ERR, "Invalid datalink type");
184 		pcap_close(hpcap);
185 		hpcap = oldhpcap;
186 		return (-1);
187 	}
188 
189 	if (oldhpcap)
190 		pcap_close(oldhpcap);
191 
192 	snaplen = pcap_snapshot(hpcap);
193 	return (0);
194 }
195 
196 int
197 reset_dump(void)
198 {
199 	struct pcap_file_header hdr;
200 	struct stat st;
201 	int tmpsnap;
202 	FILE *fp;
203 
204 	if (hpcap == NULL)
205 		return (1);
206 	if (dpcap) {
207 		pcap_dump_close(dpcap);
208 		dpcap = 0;
209 	}
210 
211 	/*
212 	 * Basically reimplement pcap_dump_open() because it truncates
213 	 * files and duplicates headers and such.
214 	 */
215 	fp = fopen(filename, "a+");
216 	if (fp == NULL) {
217 		snprintf(hpcap->errbuf, PCAP_ERRBUF_SIZE, "%s: %s",
218 		    filename, pcap_strerror(errno));
219 		logmsg(LOG_ERR, "Error: %s", pcap_geterr(hpcap));
220 		return (1);
221 	}
222 	if (fstat(fileno(fp), &st) == -1) {
223 		snprintf(hpcap->errbuf, PCAP_ERRBUF_SIZE, "%s: %s",
224 		    filename, pcap_strerror(errno));
225 		logmsg(LOG_ERR, "Error: %s", pcap_geterr(hpcap));
226 		return (1);
227 	}
228 
229 	dpcap = (pcap_dumper_t *)fp;
230 
231 #define TCPDUMP_MAGIC 0xa1b2c3d4
232 
233 	if (st.st_size == 0) {
234 		if (snaplen != pcap_snapshot(hpcap)) {
235 			logmsg(LOG_NOTICE, "Using snaplen %d", snaplen);
236 			if (init_pcap()) {
237 				logmsg(LOG_ERR, "Failed to initialize");
238 				if (hpcap == NULL) return (-1);
239 				logmsg(LOG_NOTICE, "Using old settings");
240 			}
241 		}
242 		hdr.magic = TCPDUMP_MAGIC;
243 		hdr.version_major = PCAP_VERSION_MAJOR;
244 		hdr.version_minor = PCAP_VERSION_MINOR;
245 		hdr.thiszone = hpcap->tzoff;
246 		hdr.snaplen = hpcap->snapshot;
247 		hdr.sigfigs = 0;
248 		hdr.linktype = hpcap->linktype;
249 
250 		if (fwrite((char *)&hdr, sizeof(hdr), 1, fp) != 1) {
251 			dpcap = NULL;
252 			fclose(fp);
253 			return (-1);
254 		}
255 		return (0);
256 	}
257 
258 	/*
259 	 * XXX Must read the file, compare the header against our new
260 	 * options (in particular, snaplen) and adjust our options so
261 	 * that we generate a correct file.
262 	 */
263 	(void) fseek(fp, 0L, SEEK_SET);
264 	if (fread((char *)&hdr, sizeof(hdr), 1, fp) == 1) {
265 		if (hdr.magic != TCPDUMP_MAGIC ||
266 		    hdr.version_major != PCAP_VERSION_MAJOR ||
267 		    hdr.version_minor != PCAP_VERSION_MINOR ||
268 		    hdr.linktype != hpcap->linktype) {
269 			logmsg(LOG_ERR,
270 			    "Invalid/incompatible log file, move it away");
271 			fclose(fp);
272 			return (1);
273 		    }
274 		if (hdr.snaplen != snaplen) {
275 			logmsg(LOG_WARNING,
276 			    "Existing file specifies a snaplen of %u, using it",
277 			    hdr.snaplen);
278 			tmpsnap = snaplen;
279 			snaplen = hdr.snaplen;
280 			if (init_pcap()) {
281 				logmsg(LOG_ERR, "Failed to re-initialize");
282 				if (hpcap == 0)
283 					return (-1);
284 				logmsg(LOG_NOTICE,
285 					"Using old settings, offset: %llu",
286 					(unsigned long long)st.st_size);
287 			}
288 			snaplen = tmpsnap;
289 		}
290 	}
291 
292 	(void) fseek(fp, 0L, SEEK_END);
293 	return (0);
294 }
295 
296 int
297 main(int argc, char **argv)
298 {
299 	struct pcap_stat pstat;
300 	int ch, np;
301 
302 	while ((ch = getopt(argc, argv, "Dd:s:f:")) != -1) {
303 		switch (ch) {
304 		case 'D':
305 			Debug = 1;
306 			break;
307 		case 'd':
308 			delay = atoi(optarg);
309 			if (delay < 5 || delay > 60*60)
310 				usage();
311 			break;
312 		case 'f':
313 			filename = optarg;
314 			break;
315 		case 's':
316 			snaplen = atoi(optarg);
317 			if (snaplen <= 0)
318 				snaplen = DEF_SNAPLEN;
319 			break;
320 		default:
321 			usage();
322 		}
323 
324 	}
325 
326 	log_debug = Debug;
327 	argc -= optind;
328 	argv += optind;
329 
330 	if (!Debug) {
331 		openlog("pflogd", LOG_PID | LOG_CONS, LOG_DAEMON);
332 		if (daemon(0, 0)) {
333 			logmsg(LOG_WARNING, "Failed to become daemon: %s",
334 			    strerror(errno));
335 		}
336 		pidfile(NULL);
337 	}
338 
339 	(void)umask(S_IRWXG | S_IRWXO);
340 
341 	signal(SIGTERM, sig_close);
342 	signal(SIGINT, sig_close);
343 	signal(SIGQUIT, sig_close);
344 	signal(SIGALRM, sig_alrm);
345 	signal(SIGHUP, sig_hup);
346 	alarm(delay);
347 
348 	if (argc) {
349 		filter = copy_argv(argv);
350 		if (filter == NULL)
351 			logmsg(LOG_NOTICE, "Failed to form filter expression");
352 	}
353 
354 	if (init_pcap()) {
355 		logmsg(LOG_ERR, "Exiting, init failure");
356 		exit(1);
357 	}
358 
359 	if (reset_dump()) {
360 		logmsg(LOG_ERR, "Failed to open log file %s", filename);
361 		pcap_close(hpcap);
362 		exit(1);
363 	}
364 
365 	while (1) {
366 		np = pcap_dispatch(hpcap, PCAP_NUM_PKTS, pcap_dump, (u_char *)dpcap);
367 		if (np < 0)
368 			logmsg(LOG_NOTICE, "%s", pcap_geterr(hpcap));
369 
370 		if (gotsig_close)
371 			break;
372 		if (gotsig_hup) {
373 			if (reset_dump()) {
374 				logmsg(LOG_ERR, "Failed to open log file!");
375 				break;
376 			}
377 			logmsg(LOG_NOTICE, "Reopened logfile");
378 			gotsig_hup = 0;
379 		}
380 
381 		if (gotsig_alrm) {
382 			/* XXX pcap_dumper is an incomplete type which libpcap
383 			 * casts to a FILE* currently.  For now it is safe to
384 			 * make the same assumption, however this may change
385 			 * in the future.
386 			 */
387 			if (dpcap) {
388 				if (fflush((FILE *)dpcap) == EOF) {
389 					break;
390 				}
391 			}
392 			gotsig_alrm = 0;
393 			alarm(delay);
394 		}
395 	}
396 
397 	logmsg(LOG_NOTICE, "Exiting due to signal");
398 	if (dpcap)
399 		pcap_dump_close(dpcap);
400 
401 	if (pcap_stats(hpcap, &pstat) < 0)
402 		logmsg(LOG_WARNING, "Reading stats: %s", pcap_geterr(hpcap));
403 	else
404 		logmsg(LOG_NOTICE, "%u packets received, %u dropped",
405 		    pstat.ps_recv, pstat.ps_drop);
406 
407 	pcap_close(hpcap);
408 	if (!Debug)
409 		closelog();
410 	return (0);
411 }
412