xref: /openbsd-src/usr.sbin/tcpdump/privsep_pcap.c (revision 50b7afb2c2c0993b0894d4e34bf857cb13ed9c80)
1 /*	$OpenBSD: privsep_pcap.c,v 1.17 2012/11/14 03:33:04 lteo Exp $ */
2 
3 /*
4  * Copyright (c) 2004 Can Erkin Acar
5  * All rights reserved.
6  *
7  * Redistribution and use in source and binary forms, with or without
8  * modification, are permitted provided that the following conditions
9  * are met:
10  *
11  *    - Redistributions of source code must retain the above copyright
12  *      notice, this list of conditions and the following disclaimer.
13  *    - Redistributions in binary form must reproduce the above
14  *      copyright notice, this list of conditions and the following
15  *      disclaimer in the documentation and/or other materials provided
16  *      with the distribution.
17  *
18  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
19  * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
20  * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
21  * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
22  * COPYRIGHT HOLDERS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
23  * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
24  * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
25  * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
26  * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
27  * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
28  * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
29  * POSSIBILITY OF SUCH DAMAGE.
30  */
31 
32 #include <sys/types.h>
33 #include <sys/ioctl.h>
34 #include <sys/socket.h>
35 #include <net/if.h>
36 
37 #include <err.h>
38 #include <errno.h>
39 #include <fcntl.h>
40 #include <pcap-int.h>
41 #include <pcap.h>
42 #include <stdio.h>
43 #include <stdlib.h>
44 #include <string.h>
45 #include <unistd.h>
46 
47 #include "privsep.h"
48 
49 /*
50  * privileged part of priv_pcap_setfilter, compile the filter
51  * expression, and return it to the parent. Note that we fake an hpcap
52  * and use it to capture the error messages, and pass the error back
53  * to client.
54  */
55 int
56 setfilter(int bpfd, int sock, char *filter)
57 {
58 	struct bpf_program fcode;
59 	int oflag, snap, link;
60 	u_int32_t netmask;
61 	pcap_t hpcap;
62 
63 	must_read(sock, &oflag, sizeof(oflag));
64 	must_read(sock, &netmask, sizeof(netmask));
65 	must_read(sock, &snap, sizeof(snap));
66 	must_read(sock, &link, sizeof(link));
67 
68 	if (snap < 0) {
69 		snprintf(hpcap.errbuf, PCAP_ERRBUF_SIZE, "invalid snaplen");
70 		goto err;
71 	}
72 
73 	/* fake hpcap, it only needs errbuf, snaplen, and linktype to
74 	 * compile a filter expression */
75 	/* XXX messing with pcap internals */
76 	hpcap.snapshot = snap;
77 	hpcap.linktype = link;
78 	if (pcap_compile(&hpcap, &fcode, filter, oflag, netmask))
79 		goto err;
80 
81 	/* if bpf descriptor is open, set the filter XXX check oflag? */
82 	if (bpfd >= 0 && ioctl(bpfd, BIOCSETF, &fcode)) {
83 		snprintf(hpcap.errbuf, PCAP_ERRBUF_SIZE,
84 		    "ioctl: BIOCSETF: %s", strerror(errno));
85 		pcap_freecode(&fcode);
86 		goto err;
87 	}
88 	if (fcode.bf_len > 0) {
89 		/* write the filter */
90 		must_write(sock, &fcode.bf_len, sizeof(fcode.bf_len));
91 		must_write(sock, fcode.bf_insns,
92 		    fcode.bf_len * sizeof(struct bpf_insn));
93 	} else {
94 		snprintf(hpcap.errbuf, PCAP_ERRBUF_SIZE, "Invalid filter size");
95 		pcap_freecode(&fcode);
96 		goto err;
97 	}
98 
99 
100 	pcap_freecode(&fcode);
101 	return (0);
102 
103  err:
104 	fcode.bf_len = 0;
105 	must_write(sock, &fcode.bf_len, sizeof(fcode.bf_len));
106 
107 	/* write back the error string */
108 	write_string(sock, hpcap.errbuf);
109 	return (1);
110 }
111 
112 /*
113  * filter is compiled and set in the privileged process.
114  * get the compiled output and set it locally for filtering dumps etc.
115  */
116 struct bpf_program *
117 priv_pcap_setfilter(pcap_t *hpcap, int oflag, u_int32_t netmask)
118 {
119 	struct bpf_program *fcode = NULL;
120 	int snap, link;
121 	char *ebuf;
122 
123 	if (priv_fd < 0)
124 		errx(1, "%s: called from privileged portion", __func__);
125 
126 	ebuf = pcap_geterr(hpcap);
127 	snap = pcap_snapshot(hpcap);
128 	link = pcap_datalink(hpcap);
129 
130 	fcode = calloc(1, sizeof(*fcode));
131 	if (fcode == NULL) {
132 		snprintf(ebuf, PCAP_ERRBUF_SIZE, "out of memory");
133 		return (NULL);
134 	}
135 
136 	write_command(priv_fd, PRIV_SETFILTER);
137 
138 	/* send oflag, netmask, snaplen and linktype */
139 	must_write(priv_fd, &oflag, sizeof(oflag));
140 	must_write(priv_fd, &netmask, sizeof(netmask));
141 	must_write(priv_fd, &snap, sizeof(snap));
142 	must_write(priv_fd, &link, sizeof(link));
143 
144 	/* receive compiled filter */
145 	must_read(priv_fd, &fcode->bf_len, sizeof(fcode->bf_len));
146 	if (fcode->bf_len <= 0) {
147 		int len;
148 
149 		len = read_string(priv_fd, ebuf, PCAP_ERRBUF_SIZE, __func__);
150 		if (len == 0)
151 			snprintf(ebuf, PCAP_ERRBUF_SIZE, "pcap compile error");
152 		goto err;
153 	}
154 
155 	fcode->bf_insns = calloc(fcode->bf_len, sizeof(struct bpf_insn));
156 	if (fcode->bf_insns == NULL) {
157 		snprintf(ebuf, PCAP_ERRBUF_SIZE, "out of memory");
158 		goto err;
159 	}
160 
161 	must_read(priv_fd, fcode->bf_insns,
162 	    fcode->bf_len * sizeof(struct bpf_insn));
163 
164 	pcap_setfilter(hpcap, fcode);
165 	return (fcode);
166 
167  err:
168 	free(fcode);
169 	return (NULL);
170 }
171 
172 
173 /* privileged part of priv_pcap_live */
174 int
175 pcap_live(const char *device, int snaplen, int promisc, u_int dlt,
176     u_int dirfilt)
177 {
178 	char		bpf[sizeof "/dev/bpf0000000000"];
179 	int		fd, n = 0;
180 	struct ifreq	ifr;
181 	unsigned	v;
182 
183 	if (device == NULL || snaplen <= 0)
184 		return (-1);
185 
186 	do {
187 		snprintf(bpf, sizeof(bpf), "/dev/bpf%d", n++);
188 		fd = open(bpf, O_RDONLY);
189 	} while (fd < 0 && errno == EBUSY);
190 
191 	if (fd < 0)
192 		return (-1);
193 
194 	v = 32768;	/* XXX this should be a user-accessible hook */
195 	ioctl(fd, BIOCSBLEN, &v);
196 
197 	strlcpy(ifr.ifr_name, device, sizeof(ifr.ifr_name));
198 	if (ioctl(fd, BIOCSETIF, &ifr) < 0)
199 		goto error;
200 
201 	if (dlt != (u_int) -1 && ioctl(fd, BIOCSDLT, &dlt))
202 		goto error;
203 
204 	if (promisc)
205 		/* this is allowed to fail */
206 		ioctl(fd, BIOCPROMISC, NULL);
207 	if (ioctl(fd, BIOCSDIRFILT, &dirfilt) < 0)
208 		goto error;
209 
210 	/* lock the descriptor */
211 	if (ioctl(fd, BIOCLOCK, NULL) < 0)
212 		goto error;
213 	return (fd);
214 
215  error:
216 	close(fd);
217 	return (-1);
218 }
219 
220 
221 /*
222  * XXX reimplement pcap_open_live with privsep, this is the
223  * unprivileged part.
224  */
225 pcap_t *
226 priv_pcap_live(const char *dev, int slen, int prom, int to_ms,
227     char *ebuf, u_int dlt, u_int dirfilt)
228 {
229 	int fd, err;
230 	struct bpf_version bv;
231 	u_int v;
232 	pcap_t *p;
233 
234 	if (priv_fd < 0)
235 		errx(1, "%s: called from privileged portion", __func__);
236 
237 	if (dev == NULL) {
238 		snprintf(ebuf, PCAP_ERRBUF_SIZE, "No interface specified");
239 		return (NULL);
240 	}
241 
242 	p = (pcap_t *)malloc(sizeof(*p));
243 	if (p == NULL) {
244 		snprintf(ebuf, PCAP_ERRBUF_SIZE, "malloc: %s",
245 		    pcap_strerror(errno));
246 		return (NULL);
247 	}
248 
249 	bzero(p, sizeof(*p));
250 
251 	write_command(priv_fd, PRIV_OPEN_BPF);
252 	must_write(priv_fd, &slen, sizeof(int));
253 	must_write(priv_fd, &prom, sizeof(int));
254 	must_write(priv_fd, &dlt, sizeof(u_int));
255 	must_write(priv_fd, &dirfilt, sizeof(u_int));
256 	write_string(priv_fd, dev);
257 
258 	fd = receive_fd(priv_fd);
259 	must_read(priv_fd, &err, sizeof(int));
260 	if (fd < 0) {
261 		snprintf(ebuf, PCAP_ERRBUF_SIZE,
262 		    "Failed to open bpf device for %s: %s",
263 		    dev, strerror(err));
264 		goto bad;
265 	}
266 
267 	/* fd is locked, can only use 'safe' ioctls */
268 	if (ioctl(fd, BIOCVERSION, &bv) < 0) {
269 		snprintf(ebuf, PCAP_ERRBUF_SIZE, "BIOCVERSION: %s",
270 		    pcap_strerror(errno));
271 		goto bad;
272 	}
273 
274 	if (bv.bv_major != BPF_MAJOR_VERSION ||
275 	    bv.bv_minor < BPF_MINOR_VERSION) {
276 		snprintf(ebuf, PCAP_ERRBUF_SIZE,
277 		    "kernel bpf filter out of date");
278 		goto bad;
279 	}
280 
281 	p->fd = fd;
282 	p->snapshot = slen;
283 
284 	/* Get the data link layer type. */
285 	if (ioctl(fd, BIOCGDLT, &v) < 0) {
286 		snprintf(ebuf, PCAP_ERRBUF_SIZE, "BIOCGDLT: %s",
287 		    pcap_strerror(errno));
288 		goto bad;
289 	}
290 #if _BSDI_VERSION - 0 >= 199510
291 	/* The SLIP and PPP link layer header changed in BSD/OS 2.1 */
292 	switch (v) {
293 
294 	case DLT_SLIP:
295 		v = DLT_SLIP_BSDOS;
296 		break;
297 
298 	case DLT_PPP:
299 		v = DLT_PPP_BSDOS;
300 		break;
301 	}
302 #endif
303 	p->linktype = v;
304 
305 	/* XXX hack */
306 	if (p->linktype == DLT_PFLOG && p->snapshot < 160)
307 		p->snapshot = 160;
308 
309 	/* set timeout */
310 	if (to_ms != 0) {
311 		struct timeval to;
312 		to.tv_sec = to_ms / 1000;
313 		to.tv_usec = (to_ms * 1000) % 1000000;
314 		if (ioctl(p->fd, BIOCSRTIMEOUT, &to) < 0) {
315 			snprintf(ebuf, PCAP_ERRBUF_SIZE, "BIOCSRTIMEOUT: %s",
316 			    pcap_strerror(errno));
317 			goto bad;
318 		}
319 	}
320 
321 	if (ioctl(fd, BIOCGBLEN, &v) < 0) {
322 		snprintf(ebuf, PCAP_ERRBUF_SIZE, "BIOCGBLEN: %s",
323 		    pcap_strerror(errno));
324 		goto bad;
325 	}
326 	p->bufsize = v;
327 	p->buffer = (u_char *)malloc(p->bufsize);
328 	if (p->buffer == NULL) {
329 		snprintf(ebuf, PCAP_ERRBUF_SIZE, "malloc: %s",
330 		    pcap_strerror(errno));
331 		goto bad;
332 	}
333 	return (p);
334 
335  bad:
336 	if (fd >= 0)
337 		close(fd);
338 	free(p);
339 	return (NULL);
340 }
341 
342 
343 
344 /*
345  * reimplement pcap_open_offline with privsep, this is the
346  * unprivileged part.
347  * XXX merge with above?
348  */
349 static void
350 swap_hdr(struct pcap_file_header *hp)
351 {
352 	hp->version_major = swap16(hp->version_major);
353 	hp->version_minor = swap16(hp->version_minor);
354 	hp->thiszone = swap32(hp->thiszone);
355 	hp->sigfigs = swap32(hp->sigfigs);
356 	hp->snaplen = swap32(hp->snaplen);
357 	hp->linktype = swap32(hp->linktype);
358 }
359 
360 pcap_t *
361 priv_pcap_offline(const char *fname, char *errbuf)
362 {
363 	pcap_t *p;
364 	FILE *fp = NULL;
365 	struct pcap_file_header hdr;
366 	int linklen, err;
367 
368 	if (priv_fd < 0)
369 		errx(1, "%s: called from privileged portion", __func__);
370 
371 	p = (pcap_t *)malloc(sizeof(*p));
372 	if (p == NULL) {
373 		strlcpy(errbuf, "out of swap", PCAP_ERRBUF_SIZE);
374 		return (NULL);
375 	}
376 
377 	memset((char *)p, 0, sizeof(*p));
378 
379 	if (fname[0] == '-' && fname[1] == '\0') {
380 		p->fd = -1;
381 		fp = stdin;
382 	} else {
383 		write_command(priv_fd, PRIV_OPEN_DUMP);
384 		p->fd = receive_fd(priv_fd);
385 		must_read(priv_fd, &err, sizeof(int));
386 		if (p->fd < 0) {
387 			snprintf(errbuf, PCAP_ERRBUF_SIZE,
388 			    "Failed to open input file %s: %s",
389 			    fname, strerror(err));
390 			goto bad;
391 		}
392 
393 		fp = fdopen(p->fd, "r");
394 		if (fp == NULL) {
395 			snprintf(errbuf, PCAP_ERRBUF_SIZE, "%s: %s", fname,
396 			    pcap_strerror(errno));
397 			close(p->fd);
398 			p->fd = -1;
399 			goto bad;
400 		}
401 	}
402 	if (fread((char *)&hdr, sizeof(hdr), 1, fp) != 1) {
403 		snprintf(errbuf, PCAP_ERRBUF_SIZE, "fread: %s",
404 		    pcap_strerror(errno));
405 		goto bad;
406 	}
407 
408 	if (hdr.magic != TCPDUMP_MAGIC) {
409 		if (swap32(hdr.magic) != TCPDUMP_MAGIC) {
410 			snprintf(errbuf, PCAP_ERRBUF_SIZE,
411 			    "bad dump file format");
412 			goto bad;
413 		}
414 		p->sf.swapped = 1;
415 		swap_hdr(&hdr);
416 	}
417 	if (hdr.version_major < PCAP_VERSION_MAJOR) {
418 		snprintf(errbuf, PCAP_ERRBUF_SIZE, "archaic file format");
419 		goto bad;
420 	}
421 
422 	p->tzoff = hdr.thiszone;
423 	p->snapshot = hdr.snaplen;
424 	p->linktype = hdr.linktype;
425 	p->sf.rfile = fp;
426 	p->bufsize = hdr.snaplen;
427 
428 	/* Align link header as required for proper data alignment */
429 	/* XXX should handle all types */
430 	switch (p->linktype) {
431 
432 	case DLT_EN10MB:
433 		linklen = 14;
434 		break;
435 
436 	case DLT_FDDI:
437 		linklen = 13 + 8;	/* fddi_header + llc */
438 		break;
439 
440 	case DLT_NULL:
441 	default:
442 		linklen = 0;
443 		break;
444 	}
445 
446 	if (p->bufsize < 0)
447 		p->bufsize = BPF_MAXBUFSIZE;
448 	p->sf.base = (u_char *)malloc(p->bufsize + BPF_ALIGNMENT);
449 	if (p->sf.base == NULL) {
450 		strlcpy(errbuf, "out of swap", PCAP_ERRBUF_SIZE);
451 		goto bad;
452 	}
453 	p->buffer = p->sf.base + BPF_ALIGNMENT - (linklen % BPF_ALIGNMENT);
454 	p->sf.version_major = hdr.version_major;
455 	p->sf.version_minor = hdr.version_minor;
456 #ifdef PCAP_FDDIPAD
457 	/* XXX what to do with this? */
458 	/* XXX padding only needed for kernel fcode */
459 	pcap_fddipad = 0;
460 #endif
461 	return (p);
462 
463  bad:
464 	if (fp != NULL && p->fd != -1)
465 		fclose(fp);
466 	free(p);
467 	return (NULL);
468 }
469 
470 
471 static int
472 sf_write_header(FILE *fp, int linktype, int thiszone, int snaplen)
473 {
474 	struct pcap_file_header hdr;
475 
476 	bzero(&hdr, sizeof hdr);
477 	hdr.magic = TCPDUMP_MAGIC;
478 	hdr.version_major = PCAP_VERSION_MAJOR;
479 	hdr.version_minor = PCAP_VERSION_MINOR;
480 
481 	hdr.thiszone = thiszone;
482 	hdr.snaplen = snaplen;
483 	hdr.sigfigs = 0;
484 	hdr.linktype = linktype;
485 
486 	if (fwrite((char *)&hdr, sizeof(hdr), 1, fp) != 1)
487 		return (-1);
488 
489 	return (0);
490 }
491 
492 pcap_dumper_t *
493 priv_pcap_dump_open(pcap_t *p, char *fname)
494 {
495 	int fd, err;
496 	FILE *f;
497 
498 	if (priv_fd < 0)
499 		errx(1, "%s: called from privileged portion", __func__);
500 
501 	if (fname[0] == '-' && fname[1] == '\0') {
502 		f = stdout;
503 		priv_init_done();
504 	} else {
505 		write_command(priv_fd, PRIV_OPEN_OUTPUT);
506 		fd = receive_fd(priv_fd);
507 		must_read(priv_fd, &err, sizeof(err));
508 		if (fd < 0)  {
509 			snprintf(p->errbuf, PCAP_ERRBUF_SIZE,
510 			    "Failed to open output file %s: %s",
511 			    fname, strerror(err));
512 			return (NULL);
513 		}
514 		f = fdopen(fd, "w");
515 		if (f == NULL) {
516 			snprintf(p->errbuf, PCAP_ERRBUF_SIZE, "%s: %s",
517 			    fname, pcap_strerror(errno));
518 			close(fd);
519 			return (NULL);
520 		}
521 	}
522 
523 	(void)sf_write_header(f, p->linktype, p->tzoff, p->snapshot);
524 	return ((pcap_dumper_t *)f);
525 }
526