privsep.c revision 1.1.1.4       1 /*	$NetBSD: privsep.c,v 1.1.1.4 2009/12/01 07:03:05 martti Exp $	*/
      2 /*	$OpenBSD: privsep.c,v 1.16 2006/10/25 20:55:04 moritz Exp $	*/
      3 
      4 /*
      5  * Copyright (c) 2003 Can Erkin Acar
      6  * Copyright (c) 2003 Anil Madhavapeddy <anil (at) recoil.org>
      7  *
      8  * Permission to use, copy, modify, and distribute this software for any
      9  * purpose with or without fee is hereby granted, provided that the above
     10  * copyright notice and this permission notice appear in all copies.
     11  *
     12  * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
     13  * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
     14  * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
     15  * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
     16  * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
     17  * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
     18  * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
     19  */
     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 <limits.h>
     32 #include <pcap.h>
     33 #include <pcap-int.h>
     34 #include <pwd.h>
     35 #include <signal.h>
     36 #include <stdio.h>
     37 #include <stdlib.h>
     38 #include <string.h>
     39 #include <syslog.h>
     40 #include <unistd.h>
     41 #include "pflogd.h"
     42 
     43 enum cmd_types {
     44 	PRIV_SET_SNAPLEN,	/* set the snaplength */
     45 	PRIV_MOVE_LOG,		/* move logfile away */
     46 	PRIV_OPEN_LOG		/* open logfile for appending */
     47 };
     48 
     49 static int priv_fd = -1;
     50 static volatile pid_t child_pid = -1;
     51 
     52 volatile sig_atomic_t gotsig_chld = 0;
     53 
     54 static void sig_pass_to_chld(int);
     55 static void sig_chld(int);
     56 static int  may_read(int, void *, size_t);
     57 static void must_read(int, void *, size_t);
     58 static void must_write(int, void *, size_t);
     59 static int  set_snaplen(int snap);
     60 static int  move_log(const char *name);
     61 
     62 extern char *filename;
     63 extern pcap_t *hpcap;
     64 
     65 /* based on syslogd privsep */
     66 int
     67 priv_init(void)
     68 {
     69 	int i, fd, socks[2], cmd;
     70 	int snaplen, ret, olderrno;
     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 (setresgid(pw->pw_gid, pw->pw_gid, pw->pw_gid) == -1)
    100 			err(1, "setresgid() failed");
    101 		if (setgroups(1, gidset) == -1)
    102 			err(1, "setgroups() failed");
    103 		if (setresuid(pw->pw_uid, pw->pw_uid, pw->pw_uid) == -1)
    104 			err(1, "setresuid() failed");
    105 		close(socks[0]);
    106 		priv_fd = socks[1];
    107 		return 0;
    108 	}
    109 
    110 	/* Father */
    111 	/* Pass ALRM/TERM/HUP/INT/QUIT through to child, and accept CHLD */
    112 	signal(SIGALRM, sig_pass_to_chld);
    113 	signal(SIGTERM, sig_pass_to_chld);
    114 	signal(SIGHUP,  sig_pass_to_chld);
    115 	signal(SIGINT,  sig_pass_to_chld);
    116 	signal(SIGQUIT,  sig_pass_to_chld);
    117 	signal(SIGCHLD, sig_chld);
    118 
    119 	setproctitle("[priv]");
    120 	close(socks[1]);
    121 
    122 	while (!gotsig_chld) {
    123 		if (may_read(socks[0], &cmd, sizeof(int)))
    124 			break;
    125 		switch (cmd) {
    126 		case PRIV_SET_SNAPLEN:
    127 			logmsg(LOG_DEBUG,
    128 			    "[priv]: msg PRIV_SET_SNAPLENGTH received");
    129 			must_read(socks[0], &snaplen, sizeof(int));
    130 
    131 			ret = set_snaplen(snaplen);
    132 			if (ret) {
    133 				logmsg(LOG_NOTICE,
    134 				   "[priv]: set_snaplen failed for snaplen %d",
    135 				   snaplen);
    136 			}
    137 
    138 			must_write(socks[0], &ret, sizeof(int));
    139 			break;
    140 
    141 		case PRIV_OPEN_LOG:
    142 			logmsg(LOG_DEBUG,
    143 			    "[priv]: msg PRIV_OPEN_LOG received");
    144 			/* create or append logs but do not follow symlinks */
    145 			fd = open(filename,
    146 			    O_RDWR|O_CREAT|O_APPEND|O_NONBLOCK|O_NOFOLLOW,
    147 			    0600);
    148 			olderrno = errno;
    149 			send_fd(socks[0], fd);
    150 			if (fd < 0)
    151 				logmsg(LOG_NOTICE,
    152 				    "[priv]: failed to open %s: %s",
    153 				    filename, strerror(olderrno));
    154 			else
    155 				close(fd);
    156 			break;
    157 
    158 		case PRIV_MOVE_LOG:
    159 			logmsg(LOG_DEBUG,
    160 			    "[priv]: msg PRIV_MOVE_LOG received");
    161 			ret = move_log(filename);
    162 			must_write(socks[0], &ret, sizeof(int));
    163 			break;
    164 
    165 		default:
    166 			logmsg(LOG_ERR, "[priv]: unknown command %d", cmd);
    167 			_exit(1);
    168 			/* NOTREACHED */
    169 		}
    170 	}
    171 
    172 	_exit(1);
    173 }
    174 
    175 /* this is called from parent */
    176 static int
    177 set_snaplen(int snap)
    178 {
    179 	if (hpcap == NULL)
    180 		return (1);
    181 
    182 	hpcap->snapshot = snap;
    183 	set_pcap_filter();
    184 
    185 	return 0;
    186 }
    187 
    188 static int
    189 move_log(const char *name)
    190 {
    191 	char ren[PATH_MAX];
    192 	int len;
    193 
    194 	for (;;) {
    195 		int fd;
    196 
    197 		len = snprintf(ren, sizeof(ren), "%s.bad.%08x",
    198 		    name, arc4random());
    199 		if (len >= sizeof(ren)) {
    200 			logmsg(LOG_ERR, "[priv] new name too long");
    201 			return (1);
    202 		}
    203 
    204 		/* lock destinanion */
    205 		fd = open(ren, O_CREAT|O_EXCL, 0);
    206 		if (fd >= 0) {
    207 			close(fd);
    208 			break;
    209 		}
    210 		/* if file exists, try another name */
    211 		if (errno != EEXIST && errno != EINTR) {
    212 			logmsg(LOG_ERR, "[priv] failed to create new name: %s",
    213 			    strerror(errno));
    214 			return (1);
    215 		}
    216 	}
    217 
    218 	if (rename(name, ren)) {
    219 		logmsg(LOG_ERR, "[priv] failed to rename %s to %s: %s",
    220 		    name, ren, strerror(errno));
    221 		return (1);
    222 	}
    223 
    224 	logmsg(LOG_NOTICE,
    225 	       "[priv]: log file %s moved to %s", name, ren);
    226 
    227 	return (0);
    228 }
    229 
    230 /*
    231  * send the snaplength to privileged process
    232  */
    233 int
    234 priv_set_snaplen(int snaplen)
    235 {
    236 	int cmd, ret;
    237 
    238 	if (priv_fd < 0)
    239 		errx(1, "%s: called from privileged portion", __func__);
    240 
    241 	cmd = PRIV_SET_SNAPLEN;
    242 
    243 	must_write(priv_fd, &cmd, sizeof(int));
    244 	must_write(priv_fd, &snaplen, sizeof(int));
    245 
    246 	must_read(priv_fd, &ret, sizeof(int));
    247 
    248 	/* also set hpcap->snapshot in child */
    249 	if (ret == 0)
    250 		hpcap->snapshot = snaplen;
    251 
    252 	return (ret);
    253 }
    254 
    255 /* Open log-file */
    256 int
    257 priv_open_log(void)
    258 {
    259 	int cmd, fd;
    260 
    261 	if (priv_fd < 0)
    262 		errx(1, "%s: called from privileged portion", __func__);
    263 
    264 	cmd = PRIV_OPEN_LOG;
    265 	must_write(priv_fd, &cmd, sizeof(int));
    266 	fd = receive_fd(priv_fd);
    267 
    268 	return (fd);
    269 }
    270 /* Move-away and reopen log-file */
    271 int
    272 priv_move_log(void)
    273 {
    274 	int cmd, ret;
    275 
    276 	if (priv_fd < 0)
    277 		errx(1, "%s: called from privileged portion\n", __func__);
    278 
    279 	cmd = PRIV_MOVE_LOG;
    280 	must_write(priv_fd, &cmd, sizeof(int));
    281 	must_read(priv_fd, &ret, sizeof(int));
    282 
    283 	return (ret);
    284 }
    285 
    286 /* If priv parent gets a TERM or HUP, pass it through to child instead */
    287 static void
    288 sig_pass_to_chld(int sig)
    289 {
    290 	int oerrno = errno;
    291 
    292 	if (child_pid != -1)
    293 		kill(child_pid, sig);
    294 	errno = oerrno;
    295 }
    296 
    297 /* if parent gets a SIGCHLD, it will exit */
    298 static void
    299 sig_chld(int sig)
    300 {
    301 	gotsig_chld = 1;
    302 }
    303 
    304 /* Read all data or return 1 for error.  */
    305 static int
    306 may_read(int fd, void *buf, size_t n)
    307 {
    308 	char *s = buf;
    309 	ssize_t res, pos = 0;
    310 
    311 	while (n > pos) {
    312 		res = read(fd, s + pos, n - pos);
    313 		switch (res) {
    314 		case -1:
    315 			if (errno == EINTR || errno == EAGAIN)
    316 				continue;
    317 		case 0:
    318 			return (1);
    319 		default:
    320 			pos += res;
    321 		}
    322 	}
    323 	return (0);
    324 }
    325 
    326 /* Read data with the assertion that it all must come through, or
    327  * else abort the process.  Based on atomicio() from openssh. */
    328 static void
    329 must_read(int fd, void *buf, size_t n)
    330 {
    331 	char *s = buf;
    332 	ssize_t res, pos = 0;
    333 
    334 	while (n > pos) {
    335 		res = read(fd, s + pos, n - pos);
    336 		switch (res) {
    337 		case -1:
    338 			if (errno == EINTR || errno == EAGAIN)
    339 				continue;
    340 		case 0:
    341 			_exit(0);
    342 		default:
    343 			pos += res;
    344 		}
    345 	}
    346 }
    347 
    348 /* Write data with the assertion that it all has to be written, or
    349  * else abort the process.  Based on atomicio() from openssh. */
    350 static void
    351 must_write(int fd, void *buf, size_t n)
    352 {
    353 	char *s = buf;
    354 	ssize_t res, pos = 0;
    355 
    356 	while (n > pos) {
    357 		res = write(fd, s + pos, n - pos);
    358 		switch (res) {
    359 		case -1:
    360 			if (errno == EINTR || errno == EAGAIN)
    361 				continue;
    362 		case 0:
    363 			_exit(0);
    364 		default:
    365 			pos += res;
    366 		}
    367 	}
    368 }
    369