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