1/*	$OpenBSD: privsep.c,v 1.16 2006/10/25 20:55:04 moritz 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$");
22
23#include <sys/types.h>
24#include <sys/time.h>
25#include <sys/socket.h>
26#include <sys/ioctl.h>
27
28#include <net/if.h>
29#include <net/bpf.h>
30
31#include <err.h>
32#include <errno.h>
33#include <fcntl.h>
34#include <limits.h>
35#include <pcap.h>
36#include <pcap-int.h>
37#include <pwd.h>
38#include <signal.h>
39#include <stdio.h>
40#include <stdlib.h>
41#include <string.h>
42#include <syslog.h>
43#include <unistd.h>
44#include "pflogd.h"
45
46enum cmd_types {
47	PRIV_SET_SNAPLEN,	/* set the snaplength */
48	PRIV_MOVE_LOG,		/* move logfile away */
49	PRIV_OPEN_LOG		/* open logfile for appending */
50};
51
52static int priv_fd = -1;
53static volatile pid_t child_pid = -1;
54
55volatile sig_atomic_t gotsig_chld = 0;
56
57static void sig_pass_to_chld(int);
58static void sig_chld(int);
59static int  may_read(int, void *, size_t);
60static void must_read(int, void *, size_t);
61static void must_write(int, void *, size_t);
62static int  set_snaplen(int snap);
63static int  move_log(const char *name);
64
65extern char *filename;
66extern pcap_t *hpcap;
67
68/* based on syslogd privsep */
69int
70priv_init(void)
71{
72	int i, fd, socks[2], cmd;
73	int snaplen, ret, olderrno;
74	struct passwd *pw;
75
76#ifdef __FreeBSD__
77	for (i = 1; i < NSIG; i++)
78#else
79	for (i = 1; i < _NSIG; i++)
80#endif
81		signal(i, SIG_DFL);
82
83	/* Create sockets */
84	if (socketpair(AF_LOCAL, SOCK_STREAM, PF_UNSPEC, socks) == -1)
85		err(1, "socketpair() failed");
86
87	pw = getpwnam("_pflogd");
88	if (pw == NULL)
89		errx(1, "unknown user _pflogd");
90	endpwent();
91
92	child_pid = fork();
93	if (child_pid < 0)
94		err(1, "fork() failed");
95
96	if (!child_pid) {
97		gid_t gidset[1];
98
99		/* Child - drop privileges and return */
100		if (chroot(pw->pw_dir) != 0)
101			err(1, "unable to chroot");
102		if (chdir("/") != 0)
103			err(1, "unable to chdir");
104
105		gidset[0] = pw->pw_gid;
106		if (setresgid(pw->pw_gid, pw->pw_gid, pw->pw_gid) == -1)
107			err(1, "setresgid() failed");
108		if (setgroups(1, gidset) == -1)
109			err(1, "setgroups() failed");
110		if (setresuid(pw->pw_uid, pw->pw_uid, pw->pw_uid) == -1)
111			err(1, "setresuid() failed");
112		close(socks[0]);
113		priv_fd = socks[1];
114		return 0;
115	}
116
117	/* Father */
118	/* Pass ALRM/TERM/HUP/INT/QUIT through to child, and accept CHLD */
119	signal(SIGALRM, sig_pass_to_chld);
120	signal(SIGTERM, sig_pass_to_chld);
121	signal(SIGHUP,  sig_pass_to_chld);
122	signal(SIGINT,  sig_pass_to_chld);
123	signal(SIGQUIT,  sig_pass_to_chld);
124	signal(SIGCHLD, sig_chld);
125
126	setproctitle("[priv]");
127	close(socks[1]);
128
129	while (!gotsig_chld) {
130		if (may_read(socks[0], &cmd, sizeof(int)))
131			break;
132		switch (cmd) {
133		case PRIV_SET_SNAPLEN:
134			logmsg(LOG_DEBUG,
135			    "[priv]: msg PRIV_SET_SNAPLENGTH received");
136			must_read(socks[0], &snaplen, sizeof(int));
137
138			ret = set_snaplen(snaplen);
139			if (ret) {
140				logmsg(LOG_NOTICE,
141				   "[priv]: set_snaplen failed for snaplen %d",
142				   snaplen);
143			}
144
145			must_write(socks[0], &ret, sizeof(int));
146			break;
147
148		case PRIV_OPEN_LOG:
149			logmsg(LOG_DEBUG,
150			    "[priv]: msg PRIV_OPEN_LOG received");
151			/* create or append logs but do not follow symlinks */
152			fd = open(filename,
153			    O_RDWR|O_CREAT|O_APPEND|O_NONBLOCK|O_NOFOLLOW,
154			    0600);
155			olderrno = errno;
156			send_fd(socks[0], fd);
157			if (fd < 0)
158				logmsg(LOG_NOTICE,
159				    "[priv]: failed to open %s: %s",
160				    filename, strerror(olderrno));
161			else
162				close(fd);
163			break;
164
165		case PRIV_MOVE_LOG:
166			logmsg(LOG_DEBUG,
167			    "[priv]: msg PRIV_MOVE_LOG received");
168			ret = move_log(filename);
169			must_write(socks[0], &ret, sizeof(int));
170			break;
171
172		default:
173			logmsg(LOG_ERR, "[priv]: unknown command %d", cmd);
174			_exit(1);
175			/* NOTREACHED */
176		}
177	}
178
179	_exit(1);
180}
181
182/* this is called from parent */
183static int
184set_snaplen(int snap)
185{
186	if (hpcap == NULL)
187		return (1);
188
189	hpcap->snapshot = snap;
190	set_pcap_filter();
191
192	return 0;
193}
194
195static int
196move_log(const char *name)
197{
198	char ren[PATH_MAX];
199	int len;
200
201	for (;;) {
202		int fd;
203
204		len = snprintf(ren, sizeof(ren), "%s.bad.%08x",
205		    name, arc4random());
206		if (len >= sizeof(ren)) {
207			logmsg(LOG_ERR, "[priv] new name too long");
208			return (1);
209		}
210
211		/* lock destinanion */
212		fd = open(ren, O_CREAT|O_EXCL, 0);
213		if (fd >= 0) {
214			close(fd);
215			break;
216		}
217		/* if file exists, try another name */
218		if (errno != EEXIST && errno != EINTR) {
219			logmsg(LOG_ERR, "[priv] failed to create new name: %s",
220			    strerror(errno));
221			return (1);
222		}
223	}
224
225	if (rename(name, ren)) {
226		logmsg(LOG_ERR, "[priv] failed to rename %s to %s: %s",
227		    name, ren, strerror(errno));
228		return (1);
229	}
230
231	logmsg(LOG_NOTICE,
232	       "[priv]: log file %s moved to %s", name, ren);
233
234	return (0);
235}
236
237/*
238 * send the snaplength to privileged process
239 */
240int
241priv_set_snaplen(int snaplen)
242{
243	int cmd, ret;
244
245	if (priv_fd < 0)
246		errx(1, "%s: called from privileged portion", __func__);
247
248	cmd = PRIV_SET_SNAPLEN;
249
250	must_write(priv_fd, &cmd, sizeof(int));
251	must_write(priv_fd, &snaplen, sizeof(int));
252
253	must_read(priv_fd, &ret, sizeof(int));
254
255	/* also set hpcap->snapshot in child */
256	if (ret == 0)
257		hpcap->snapshot = snaplen;
258
259	return (ret);
260}
261
262/* Open log-file */
263int
264priv_open_log(void)
265{
266	int cmd, fd;
267
268	if (priv_fd < 0)
269		errx(1, "%s: called from privileged portion", __func__);
270
271	cmd = PRIV_OPEN_LOG;
272	must_write(priv_fd, &cmd, sizeof(int));
273	fd = receive_fd(priv_fd);
274
275	return (fd);
276}
277/* Move-away and reopen log-file */
278int
279priv_move_log(void)
280{
281	int cmd, ret;
282
283	if (priv_fd < 0)
284		errx(1, "%s: called from privileged portion\n", __func__);
285
286	cmd = PRIV_MOVE_LOG;
287	must_write(priv_fd, &cmd, sizeof(int));
288	must_read(priv_fd, &ret, sizeof(int));
289
290	return (ret);
291}
292
293/* If priv parent gets a TERM or HUP, pass it through to child instead */
294static void
295sig_pass_to_chld(int sig)
296{
297	int oerrno = errno;
298
299	if (child_pid != -1)
300		kill(child_pid, sig);
301	errno = oerrno;
302}
303
304/* if parent gets a SIGCHLD, it will exit */
305static void
306sig_chld(int sig)
307{
308	gotsig_chld = 1;
309}
310
311/* Read all data or return 1 for error.  */
312static int
313may_read(int fd, void *buf, size_t n)
314{
315	char *s = buf;
316	ssize_t res, pos = 0;
317
318	while (n > pos) {
319		res = read(fd, s + pos, n - pos);
320		switch (res) {
321		case -1:
322			if (errno == EINTR || errno == EAGAIN)
323				continue;
324		case 0:
325			return (1);
326		default:
327			pos += res;
328		}
329	}
330	return (0);
331}
332
333/* Read data with the assertion that it all must come through, or
334 * else abort the process.  Based on atomicio() from openssh. */
335static void
336must_read(int fd, void *buf, size_t n)
337{
338	char *s = buf;
339	ssize_t res, pos = 0;
340
341	while (n > pos) {
342		res = read(fd, s + pos, n - pos);
343		switch (res) {
344		case -1:
345			if (errno == EINTR || errno == EAGAIN)
346				continue;
347		case 0:
348			_exit(0);
349		default:
350			pos += res;
351		}
352	}
353}
354
355/* Write data with the assertion that it all has to be written, or
356 * else abort the process.  Based on atomicio() from openssh. */
357static void
358must_write(int fd, void *buf, size_t n)
359{
360	char *s = buf;
361	ssize_t res, pos = 0;
362
363	while (n > pos) {
364		res = write(fd, s + pos, n - pos);
365		switch (res) {
366		case -1:
367			if (errno == EINTR || errno == EAGAIN)
368				continue;
369		case 0:
370			_exit(0);
371		default:
372			pos += res;
373		}
374	}
375}
376