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