privsep.c revision 145840
1/*	$OpenBSD: privsep.c,v 1.13 2004/12/22 09:21:02 otto Exp $	*/
2
3/*
4 * Copyright (c) 2003 Can Erkin Acar
5 * Copyright (c) 2003 Anil Madhavapeddy <anil@recoil.org>
6 *
7 * Permission to use, copy, modify, and distribute this software for any
8 * purpose with or without fee is hereby granted, provided that the above
9 * copyright notice and this permission notice appear in all copies.
10 *
11 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
12 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
13 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
14 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
15 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
16 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
17 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
18 */
19
20#include <sys/cdefs.h>
21__FBSDID("$FreeBSD: head/contrib/pf/pflogd/privsep.c 145840 2005-05-03 16:55:20Z mlaier $");
22
23#include <sys/param.h>
24#include <sys/time.h>
25#include <sys/socket.h>
26
27#include <net/if.h>
28#include <net/bpf.h>
29
30#include <err.h>
31#include <errno.h>
32#include <fcntl.h>
33#include <pwd.h>
34#include <signal.h>
35#include <stdio.h>
36#include <stdlib.h>
37#include <string.h>
38#include <pcap.h>
39#include <pcap-int.h>
40#include <syslog.h>
41#include <unistd.h>
42#include "pflogd.h"
43
44enum cmd_types {
45	PRIV_SET_SNAPLEN,	/* set the snaplength */
46	PRIV_OPEN_LOG		/* open logfile for appending */
47};
48
49static int priv_fd = -1;
50static volatile pid_t child_pid = -1;
51
52volatile sig_atomic_t gotsig_chld = 0;
53
54static void sig_pass_to_chld(int);
55static void sig_chld(int);
56static int  may_read(int, void *, size_t);
57static void must_read(int, void *, size_t);
58static void must_write(int, void *, size_t);
59static int  set_snaplen(int snap);
60
61/* bpf filter expression common to parent and child */
62extern char *filter;
63extern char *errbuf;
64extern char *filename;
65extern pcap_t *hpcap;
66
67/* based on syslogd privsep */
68int
69priv_init(void)
70{
71	int i, fd, socks[2], cmd;
72	int snaplen, ret, olderrno;
73	struct passwd *pw;
74
75#ifdef __FreeBSD__
76	for (i = 1; i < NSIG; i++)
77#else
78	for (i = 1; i < _NSIG; i++)
79#endif
80		signal(i, SIG_DFL);
81
82	/* Create sockets */
83	if (socketpair(AF_LOCAL, SOCK_STREAM, PF_UNSPEC, socks) == -1)
84		err(1, "socketpair() failed");
85
86	pw = getpwnam("_pflogd");
87	if (pw == NULL)
88		errx(1, "unknown user _pflogd");
89	endpwent();
90
91	child_pid = fork();
92	if (child_pid < 0)
93		err(1, "fork() failed");
94
95	if (!child_pid) {
96		gid_t gidset[1];
97
98		/* Child - drop privileges and return */
99		if (chroot(pw->pw_dir) != 0)
100			err(1, "unable to chroot");
101		if (chdir("/") != 0)
102			err(1, "unable to chdir");
103
104		gidset[0] = pw->pw_gid;
105		if (setgroups(1, gidset) == -1)
106			err(1, "setgroups() failed");
107		if (setegid(pw->pw_gid) == -1)
108			err(1, "setegid() failed");
109		if (setgid(pw->pw_gid) == -1)
110			err(1, "setgid() failed");
111		if (seteuid(pw->pw_uid) == -1)
112			err(1, "seteuid() failed");
113		if (setuid(pw->pw_uid) == -1)
114			err(1, "setuid() failed");
115		close(socks[0]);
116		priv_fd = socks[1];
117		return 0;
118	}
119
120	/* Father */
121	/* Pass ALRM/TERM/HUP/INT/QUIT through to child, and accept CHLD */
122	signal(SIGALRM, sig_pass_to_chld);
123	signal(SIGTERM, sig_pass_to_chld);
124	signal(SIGHUP,  sig_pass_to_chld);
125	signal(SIGINT,  sig_pass_to_chld);
126	signal(SIGQUIT,  sig_pass_to_chld);
127	signal(SIGCHLD, sig_chld);
128
129	setproctitle("[priv]");
130	close(socks[1]);
131
132	while (!gotsig_chld) {
133		if (may_read(socks[0], &cmd, sizeof(int)))
134			break;
135		switch (cmd) {
136		case PRIV_SET_SNAPLEN:
137			logmsg(LOG_DEBUG,
138			    "[priv]: msg PRIV_SET_SNAPLENGTH received");
139			must_read(socks[0], &snaplen, sizeof(int));
140
141			ret = set_snaplen(snaplen);
142			if (ret) {
143				logmsg(LOG_NOTICE,
144				   "[priv]: set_snaplen failed for snaplen %d",
145				   snaplen);
146			}
147
148			must_write(socks[0], &ret, sizeof(int));
149			break;
150
151		case PRIV_OPEN_LOG:
152			logmsg(LOG_DEBUG,
153			    "[priv]: msg PRIV_OPEN_LOG received");
154			/* create or append logs but do not follow symlinks */
155			fd = open(filename,
156			    O_RDWR|O_CREAT|O_APPEND|O_NONBLOCK|O_NOFOLLOW,
157			    0600);
158			olderrno = errno;
159			send_fd(socks[0], fd);
160			if (fd < 0)
161				logmsg(LOG_NOTICE,
162				    "[priv]: failed to open %s: %s",
163				    filename, strerror(olderrno));
164			else
165				close(fd);
166			break;
167
168		default:
169			logmsg(LOG_ERR, "[priv]: unknown command %d", cmd);
170			_exit(1);
171			/* NOTREACHED */
172		}
173	}
174
175	_exit(1);
176}
177
178/* this is called from parent */
179static int
180set_snaplen(int snap)
181{
182	if (hpcap == NULL)
183		return (1);
184
185	hpcap->snapshot = snap;
186	set_pcap_filter();
187
188	return 0;
189}
190
191
192/*
193 * send the snaplength to privileged process
194 */
195int
196priv_set_snaplen(int snaplen)
197{
198	int cmd, ret;
199
200	if (priv_fd < 0)
201		errx(1, "%s: called from privileged portion", __func__);
202
203	cmd = PRIV_SET_SNAPLEN;
204
205	must_write(priv_fd, &cmd, sizeof(int));
206	must_write(priv_fd, &snaplen, sizeof(int));
207
208	must_read(priv_fd, &ret, sizeof(int));
209
210	/* also set hpcap->snapshot in child */
211	if (ret == 0)
212		hpcap->snapshot = snaplen;
213
214	return (ret);
215}
216
217/* Open log-file */
218int
219priv_open_log(void)
220{
221	int cmd, fd;
222
223	if (priv_fd < 0)
224		errx(1, "%s: called from privileged portion", __func__);
225
226	cmd = PRIV_OPEN_LOG;
227	must_write(priv_fd, &cmd, sizeof(int));
228	fd = receive_fd(priv_fd);
229
230	return (fd);
231}
232
233/* If priv parent gets a TERM or HUP, pass it through to child instead */
234static void
235sig_pass_to_chld(int sig)
236{
237	int oerrno = errno;
238
239	if (child_pid != -1)
240		kill(child_pid, sig);
241	errno = oerrno;
242}
243
244/* if parent gets a SIGCHLD, it will exit */
245static void
246sig_chld(int sig)
247{
248	gotsig_chld = 1;
249}
250
251/* Read all data or return 1 for error.  */
252static int
253may_read(int fd, void *buf, size_t n)
254{
255	char *s = buf;
256	ssize_t res, pos = 0;
257
258	while (n > pos) {
259		res = read(fd, s + pos, n - pos);
260		switch (res) {
261		case -1:
262			if (errno == EINTR || errno == EAGAIN)
263				continue;
264		case 0:
265			return (1);
266		default:
267			pos += res;
268		}
269	}
270	return (0);
271}
272
273/* Read data with the assertion that it all must come through, or
274 * else abort the process.  Based on atomicio() from openssh. */
275static void
276must_read(int fd, void *buf, size_t n)
277{
278	char *s = buf;
279	ssize_t res, pos = 0;
280
281	while (n > pos) {
282		res = read(fd, s + pos, n - pos);
283		switch (res) {
284		case -1:
285			if (errno == EINTR || errno == EAGAIN)
286				continue;
287		case 0:
288			_exit(0);
289		default:
290			pos += res;
291		}
292	}
293}
294
295/* Write data with the assertion that it all has to be written, or
296 * else abort the process.  Based on atomicio() from openssh. */
297static void
298must_write(int fd, void *buf, size_t n)
299{
300	char *s = buf;
301	ssize_t res, pos = 0;
302
303	while (n > pos) {
304		res = write(fd, s + pos, n - pos);
305		switch (res) {
306		case -1:
307			if (errno == EINTR || errno == EAGAIN)
308				continue;
309		case 0:
310			_exit(0);
311		default:
312			pos += res;
313		}
314	}
315}
316