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