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