privsep.c revision 130615
1/*	$OpenBSD: privsep.c,v 1.8 2004/03/14 19:17:05 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#include <sys/ioctl.h>
20#include <sys/types.h>
21#include <sys/time.h>
22#include <sys/socket.h>
23#include <sys/ioctl.h>
24
25#include <net/if.h>
26#include <net/bpf.h>
27
28#include <err.h>
29#include <errno.h>
30#include <fcntl.h>
31#include <pcap.h>
32#include <pcap-int.h>
33#include <pwd.h>
34#include <signal.h>
35#include <stdio.h>
36#include <stdlib.h>
37#include <string.h>
38#include <syslog.h>
39#include <unistd.h>
40#include "pflogd.h"
41
42enum cmd_types {
43	PRIV_SET_SNAPLEN,	/* set the snaplength */
44	PRIV_OPEN_LOG		/* open logfile for appending */
45};
46
47static int priv_fd = -1;
48static volatile pid_t child_pid = -1;
49
50volatile sig_atomic_t gotsig_chld = 0;
51
52static void sig_pass_to_chld(int);
53static void sig_chld(int);
54static int  may_read(int, void *, size_t);
55static void must_read(int, void *, size_t);
56static void must_write(int, void *, size_t);
57static int  set_snaplen(int snap);
58
59/* bpf filter expression common to parent and child */
60extern char *filter;
61extern char *errbuf;
62extern char *filename;
63extern pcap_t *hpcap;
64
65/* based on syslogd privsep */
66int
67priv_init(void)
68{
69	int i, fd, socks[2], cmd;
70	int snaplen, ret;
71	struct passwd *pw;
72
73	for (i = 1; i < _NSIG; i++)
74		signal(i, SIG_DFL);
75
76	/* Create sockets */
77	if (socketpair(AF_LOCAL, SOCK_STREAM, PF_UNSPEC, socks) == -1)
78		err(1, "socketpair() failed");
79
80	pw = getpwnam("_pflogd");
81	if (pw == NULL)
82		errx(1, "unknown user _pflogd");
83	endpwent();
84
85	child_pid = fork();
86	if (child_pid < 0)
87		err(1, "fork() failed");
88
89	if (!child_pid) {
90		gid_t gidset[1];
91
92		/* Child - drop privileges and return */
93		if (chroot(pw->pw_dir) != 0)
94			err(1, "unable to chroot");
95		if (chdir("/") != 0)
96			err(1, "unable to chdir");
97
98		gidset[0] = pw->pw_gid;
99		if (setgroups(1, gidset) == -1)
100			err(1, "setgroups() failed");
101		if (setegid(pw->pw_gid) == -1)
102			err(1, "setegid() failed");
103		if (setgid(pw->pw_gid) == -1)
104			err(1, "setgid() failed");
105		if (seteuid(pw->pw_uid) == -1)
106			err(1, "seteuid() failed");
107		if (setuid(pw->pw_uid) == -1)
108			err(1, "setuid() failed");
109		close(socks[0]);
110		priv_fd = socks[1];
111		return 0;
112	}
113
114	/* Father */
115	/* Pass ALRM/TERM/HUP through to child, and accept CHLD */
116	signal(SIGALRM, sig_pass_to_chld);
117	signal(SIGTERM, sig_pass_to_chld);
118	signal(SIGHUP,  sig_pass_to_chld);
119	signal(SIGCHLD, sig_chld);
120
121	setproctitle("[priv]");
122	close(socks[1]);
123
124	while (!gotsig_chld) {
125		if (may_read(socks[0], &cmd, sizeof(int)))
126			break;
127		switch (cmd) {
128		case PRIV_SET_SNAPLEN:
129			logmsg(LOG_DEBUG,
130			    "[priv]: msg PRIV_SET_SNAPLENGTH received");
131			must_read(socks[0], &snaplen, sizeof(int));
132
133			ret = set_snaplen(snaplen);
134			if (ret) {
135				logmsg(LOG_NOTICE,
136				   "[priv]: set_snaplen failed for snaplen %d",
137				   snaplen);
138			}
139
140			must_write(socks[0], &ret, sizeof(int));
141			break;
142
143		case PRIV_OPEN_LOG:
144			logmsg(LOG_DEBUG,
145			    "[priv]: msg PRIV_OPEN_LOG received");
146			/* create or append logs but do not follow symlinks */
147			fd = open(filename,
148			    O_RDWR|O_CREAT|O_APPEND|O_NONBLOCK|O_NOFOLLOW,
149			    0600);
150			if (fd < 0)
151				logmsg(LOG_NOTICE,
152				    "[priv]: failed to open %s: %s",
153				    filename, strerror(errno));
154			send_fd(socks[0], fd);
155			close(fd);
156			break;
157
158		default:
159			logmsg(LOG_ERR, "[priv]: unknown command %d", cmd);
160			_exit(1);
161			/* NOTREACHED */
162		}
163	}
164
165	_exit(1);
166}
167
168/* this is called from parent */
169static int
170set_snaplen(int snap)
171{
172	if (hpcap == NULL)
173		return (1);
174
175	hpcap->snapshot = snap;
176	set_pcap_filter();
177
178	return 0;
179}
180
181
182/*
183 * send the snaplength to privileged process
184 */
185int
186priv_set_snaplen(int snaplen)
187{
188	int cmd, ret;
189
190	if (priv_fd < 0)
191		errx(1, "%s: called from privileged portion", __func__);
192
193	cmd = PRIV_SET_SNAPLEN;
194
195	must_write(priv_fd, &cmd, sizeof(int));
196	must_write(priv_fd, &snaplen, sizeof(int));
197
198	must_read(priv_fd, &ret, sizeof(int));
199
200	/* also set hpcap->snapshot in child */
201	if (ret == 0)
202		hpcap->snapshot = snaplen;
203
204	return (ret);
205}
206
207/* Open log-file */
208int
209priv_open_log(void)
210{
211	int cmd, fd;
212
213	if (priv_fd < 0)
214		errx(1, "%s: called from privileged portion\n", __func__);
215
216	cmd = PRIV_OPEN_LOG;
217	must_write(priv_fd, &cmd, sizeof(int));
218	fd = receive_fd(priv_fd);
219
220	return (fd);
221}
222
223/* If priv parent gets a TERM or HUP, pass it through to child instead */
224static void
225sig_pass_to_chld(int sig)
226{
227	int oerrno = errno;
228
229	if (child_pid != -1)
230		kill(child_pid, sig);
231	errno = oerrno;
232}
233
234/* if parent gets a SIGCHLD, it will exit */
235static void
236sig_chld(int sig)
237{
238	gotsig_chld = 1;
239}
240
241/* Read all data or return 1 for error.  */
242static int
243may_read(int fd, void *buf, size_t n)
244{
245	char *s = buf;
246	ssize_t res, pos = 0;
247
248	while (n > pos) {
249		res = read(fd, s + pos, n - pos);
250		switch (res) {
251		case -1:
252			if (errno == EINTR || errno == EAGAIN)
253				continue;
254		case 0:
255			return (1);
256		default:
257			pos += res;
258		}
259	}
260	return (0);
261}
262
263/* Read data with the assertion that it all must come through, or
264 * else abort the process.  Based on atomicio() from openssh. */
265static void
266must_read(int fd, void *buf, size_t n)
267{
268	char *s = buf;
269	ssize_t res, pos = 0;
270
271	while (n > pos) {
272		res = read(fd, s + pos, n - pos);
273		switch (res) {
274		case -1:
275			if (errno == EINTR || errno == EAGAIN)
276				continue;
277		case 0:
278			_exit(0);
279		default:
280			pos += res;
281		}
282	}
283}
284
285/* Write data with the assertion that it all has to be written, or
286 * else abort the process.  Based on atomicio() from openssh. */
287static void
288must_write(int fd, void *buf, size_t n)
289{
290	char *s = buf;
291	ssize_t res, pos = 0;
292
293	while (n > pos) {
294		res = write(fd, s + pos, n - pos);
295		switch (res) {
296		case -1:
297			if (errno == EINTR || errno == EAGAIN)
298				continue;
299		case 0:
300			_exit(0);
301		default:
302			pos += res;
303		}
304	}
305}
306