1*c1f8818cSbluhm /* $OpenBSD: privsep.c,v 1.77 2023/10/12 22:36:54 bluhm Exp $ */
238182445Savsm
338182445Savsm /*
438182445Savsm * Copyright (c) 2003 Anil Madhavapeddy <anil@recoil.org>
5ece520b1Sbluhm * Copyright (c) 2016 Alexander Bluhm <bluhm@openbsd.org>
638182445Savsm *
738182445Savsm * Permission to use, copy, modify, and distribute this software for any
838182445Savsm * purpose with or without fee is hereby granted, provided that the above
938182445Savsm * copyright notice and this permission notice appear in all copies.
1038182445Savsm *
1138182445Savsm * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
1238182445Savsm * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
1338182445Savsm * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
1438182445Savsm * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
1538182445Savsm * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
1638182445Savsm * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
1738182445Savsm * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
1838182445Savsm */
19b9fc9a72Sderaadt
2038182445Savsm #include <sys/queue.h>
2138182445Savsm #include <sys/stat.h>
2238182445Savsm #include <sys/wait.h>
2338bc45fcSbluhm
2438182445Savsm #include <err.h>
2538182445Savsm #include <errno.h>
2638182445Savsm #include <fcntl.h>
2738bc45fcSbluhm #include <limits.h>
2838182445Savsm #include <netdb.h>
2938182445Savsm #include <paths.h>
3038182445Savsm #include <pwd.h>
3138182445Savsm #include <signal.h>
3238182445Savsm #include <stdio.h>
3338182445Savsm #include <stdlib.h>
3438182445Savsm #include <string.h>
3538182445Savsm #include <unistd.h>
3638182445Savsm #include <utmp.h>
3738bc45fcSbluhm
38ed00ae78Sbluhm #include "log.h"
3938182445Savsm #include "syslogd.h"
4038182445Savsm
4138182445Savsm /*
4238182445Savsm * syslogd can only go forward in these states; each state should represent
4338182445Savsm * less privilege. After STATE_INIT, the child is allowed to parse its
4438182445Savsm * config file once, and communicate the information regarding what logfiles
4538182445Savsm * it needs access to back to the parent. When that is done, it sends a
4638182445Savsm * message to the priv parent revoking this access, moving to STATE_RUNNING.
4738182445Savsm * In this state, any log-files not in the access list are rejected.
4838182445Savsm *
4938182445Savsm * This allows a HUP signal to the child to reopen its log files, and
5038182445Savsm * the config file to be parsed if it hasn't been changed (this is still
519ba52c13Savsm * useful to force resolution of remote syslog servers again).
5238182445Savsm * If the config file has been modified, then the child dies, and
5338182445Savsm * the priv parent restarts itself.
5438182445Savsm */
5538182445Savsm enum priv_state {
5638182445Savsm STATE_INIT, /* just started up */
5738182445Savsm STATE_CONFIG, /* parsing config file for first time */
5838182445Savsm STATE_RUNNING, /* running and accepting network traffic */
595b2985feSderaadt STATE_QUIT /* shutting down */
6038182445Savsm };
6138182445Savsm
6238182445Savsm enum cmd_types {
6338182445Savsm PRIV_OPEN_TTY, /* open terminal or console device */
6438182445Savsm PRIV_OPEN_LOG, /* open logfile for appending */
6547607485Shenning PRIV_OPEN_PIPE, /* fork & exec child that gets logs on stdin */
6638182445Savsm PRIV_OPEN_UTMP, /* open utmp for reading only */
6738182445Savsm PRIV_OPEN_CONFIG, /* open config file for reading only */
6838182445Savsm PRIV_CONFIG_MODIFIED, /* check if config file has been modified */
692fa726a7Sbluhm PRIV_GETADDRINFO, /* resolve host/service names */
70bd6df507Sbluhm PRIV_GETNAMEINFO, /* resolve numeric address into hostname */
71c572cfa7Sbluhm PRIV_DONE_CONFIG_PARSE /* signal that initial config parse is done */
7238182445Savsm };
7338182445Savsm
7438182445Savsm static int priv_fd = -1;
751502b2edSotto static volatile pid_t child_pid = -1;
7638182445Savsm static volatile sig_atomic_t cur_state = STATE_INIT;
7738182445Savsm
7838182445Savsm /* Queue for the allowed logfiles */
7938182445Savsm struct logname {
80b9fc9a72Sderaadt char path[PATH_MAX];
8138182445Savsm TAILQ_ENTRY(logname) next;
8238182445Savsm };
8338182445Savsm static TAILQ_HEAD(, logname) lognames;
8438182445Savsm
8538182445Savsm static void check_log_name(char *, size_t);
8647607485Shenning static int open_file(char *);
8747607485Shenning static int open_pipe(char *);
8838182445Savsm static void check_tty_name(char *, size_t);
8938182445Savsm static void increase_state(int);
9038182445Savsm static void sig_pass_to_chld(int);
9138182445Savsm static void sig_got_chld(int);
9238182445Savsm static void must_read(int, void *, size_t);
9338182445Savsm static void must_write(int, void *, size_t);
94927e6d74Scanacar static int may_read(int, void *, size_t);
9538182445Savsm
96f22826eaSbluhm static struct passwd *pw;
97f22826eaSbluhm
98ece520b1Sbluhm void
priv_init(int lockfd,int nullfd,int argc,char * argv[])99ece520b1Sbluhm priv_init(int lockfd, int nullfd, int argc, char *argv[])
10038182445Savsm {
101ece520b1Sbluhm int i, socks[2];
10236b99be9Sbluhm char *execpath, childnum[11], **privargv;
1031502b2edSotto
10438182445Savsm /* Create sockets */
10538182445Savsm if (socketpair(AF_LOCAL, SOCK_STREAM, PF_UNSPEC, socks) == -1)
10638182445Savsm err(1, "socketpair() failed");
10738182445Savsm
10838182445Savsm pw = getpwnam("_syslogd");
10938182445Savsm if (pw == NULL)
11038182445Savsm errx(1, "unknown user _syslogd");
11138182445Savsm
11238182445Savsm child_pid = fork();
113df69c215Sderaadt if (child_pid == -1)
11438182445Savsm err(1, "fork() failed");
11538182445Savsm
11638182445Savsm if (!child_pid) {
11738182445Savsm /* Child - drop privileges and return */
11838182445Savsm if (chroot(pw->pw_dir) != 0)
11936b99be9Sbluhm err(1, "chroot %s", pw->pw_dir);
120769143deSotto if (chdir("/") != 0)
12136b99be9Sbluhm err(1, "chdir %s", pw->pw_dir);
12249837e0fSderaadt
12352da46bbSdjm if (setgroups(1, &pw->pw_gid) == -1)
12449837e0fSderaadt err(1, "setgroups() failed");
12552da46bbSdjm if (setresgid(pw->pw_gid, pw->pw_gid, pw->pw_gid) == -1)
12652da46bbSdjm err(1, "setresgid() failed");
12752da46bbSdjm if (setresuid(pw->pw_uid, pw->pw_uid, pw->pw_uid) == -1)
12852da46bbSdjm err(1, "setresuid() failed");
12938182445Savsm close(socks[0]);
13038182445Savsm priv_fd = socks[1];
131ece520b1Sbluhm return;
13238182445Savsm }
133ece520b1Sbluhm close(socks[1]);
134c9ec0abeSbluhm
13536b99be9Sbluhm if (strchr(argv[0], '/') == NULL)
13636b99be9Sbluhm execpath = argv[0];
13736b99be9Sbluhm else if ((execpath = realpath(argv[0], NULL)) == NULL)
13836b99be9Sbluhm err(1, "realpath %s", argv[0]);
13936b99be9Sbluhm if (chdir("/") != 0)
14036b99be9Sbluhm err(1, "chdir /");
14136b99be9Sbluhm
14238182445Savsm if (!Debug) {
1433b557aedSderaadt close(lockfd);
14438182445Savsm dup2(nullfd, STDIN_FILENO);
14538182445Savsm dup2(nullfd, STDOUT_FILENO);
14638182445Savsm dup2(nullfd, STDERR_FILENO);
14738182445Savsm }
14838182445Savsm if (nullfd > 2)
14938182445Savsm close(nullfd);
15038182445Savsm
151ece520b1Sbluhm if (dup3(socks[0], 3, 0) == -1)
152ece520b1Sbluhm err(1, "dup3 priv sock failed");
153f379a153Sbluhm if (closefrom(4) == -1)
154f379a153Sbluhm err(1, "closefrom 4 failed");
155f379a153Sbluhm
156ece520b1Sbluhm snprintf(childnum, sizeof(childnum), "%d", child_pid);
157ece520b1Sbluhm if ((privargv = reallocarray(NULL, argc + 3, sizeof(char *))) == NULL)
158ece520b1Sbluhm err(1, "alloc priv argv failed");
15936b99be9Sbluhm privargv[0] = execpath;
16036b99be9Sbluhm for (i = 1; i < argc; i++)
161ece520b1Sbluhm privargv[i] = argv[i];
162ece520b1Sbluhm privargv[i++] = "-P";
163ece520b1Sbluhm privargv[i++] = childnum;
164ece520b1Sbluhm privargv[i++] = NULL;
165ece520b1Sbluhm execvp(privargv[0], privargv);
166ece520b1Sbluhm err(1, "exec priv '%s' failed", privargv[0]);
167ece520b1Sbluhm }
16838182445Savsm
169ece520b1Sbluhm __dead void
priv_exec(char * conf,int numeric,int child,int argc,char * argv[])170ece520b1Sbluhm priv_exec(char *conf, int numeric, int child, int argc, char *argv[])
171ece520b1Sbluhm {
172ece520b1Sbluhm int i, fd, sock, cmd, addr_len, result, restart;
173ece520b1Sbluhm size_t path_len, protoname_len, hostname_len, servname_len;
174ece520b1Sbluhm char path[PATH_MAX], protoname[5];
175ece520b1Sbluhm char hostname[NI_MAXHOST], servname[NI_MAXSERV];
176ece520b1Sbluhm struct sockaddr_storage addr;
177ece520b1Sbluhm struct stat cf_info, cf_stat;
178ece520b1Sbluhm struct addrinfo hints, *res0;
179ece520b1Sbluhm struct sigaction sa;
180e733ee17Sbluhm sigset_t sigmask;
181ece520b1Sbluhm
182f22826eaSbluhm /* Redo the password lookup after re-exec of the privsep parent. */
183f22826eaSbluhm pw = getpwnam("_syslogd");
184f22826eaSbluhm if (pw == NULL)
185f22826eaSbluhm errx(1, "unknown user _syslogd");
186f22826eaSbluhm
1878d92522dSderaadt if (unveil(conf, "r") == -1)
188bc5a8259Sbeck err(1, "unveil %s", conf);
1898d92522dSderaadt if (unveil(_PATH_UTMP, "r") == -1)
190bc5a8259Sbeck err(1, "unveil %s", _PATH_UTMP);
1918d92522dSderaadt if (unveil(_PATH_DEV, "rw") == -1)
192bc5a8259Sbeck err(1, "unveil %s", _PATH_DEV);
1931a7ae0c4Sbluhm if (unveil(_PATH_LOGPID, "c") == -1)
194bc5a8259Sbeck err(1, "unveil %s", _PATH_LOGPID);
1958d92522dSderaadt
1968d92522dSderaadt /* for pipes */
1978d92522dSderaadt if (unveil(_PATH_BSHELL, "x") == -1)
198bc5a8259Sbeck err(1, "unveil %s", _PATH_BSHELL);
1998d92522dSderaadt
2008d92522dSderaadt /* For HUP / re-exec */
2018d92522dSderaadt if (unveil("/usr/sbin/syslogd", "x") == -1)
202bc5a8259Sbeck err(1, "unveil /usr/sbin/syslogd");
2038d92522dSderaadt if (argv[0][0] == '/')
2048d92522dSderaadt if (unveil(argv[0], "x") == -1)
205bc5a8259Sbeck err(1, "unveil %s", argv[0]);
2068d92522dSderaadt
2078d92522dSderaadt if (pledge("stdio unveil rpath wpath cpath dns sendfd id proc exec",
208ece520b1Sbluhm NULL) == -1)
209ece520b1Sbluhm err(1, "pledge priv");
210ece520b1Sbluhm
211ece520b1Sbluhm if (argc <= 2 || strcmp("-P", argv[argc - 2]) != 0)
212ece520b1Sbluhm errx(1, "exec without priv");
213ece520b1Sbluhm argv[argc -= 2] = NULL;
214ece520b1Sbluhm
215ece520b1Sbluhm sock = 3;
216ece520b1Sbluhm closefrom(4);
217ece520b1Sbluhm
218ece520b1Sbluhm child_pid = child;
219ece520b1Sbluhm
220ece520b1Sbluhm memset(&sa, 0, sizeof(sa));
221ece520b1Sbluhm sigemptyset(&sa.sa_mask);
222ece520b1Sbluhm sa.sa_flags = SA_RESTART;
223ece520b1Sbluhm sa.sa_handler = SIG_DFL;
224ece520b1Sbluhm for (i = 1; i < _NSIG; i++)
225ece520b1Sbluhm sigaction(i, &sa, NULL);
226ece520b1Sbluhm
227ece520b1Sbluhm /* Pass TERM/HUP/INT/QUIT through to child, and accept CHLD */
228ece520b1Sbluhm sa.sa_handler = sig_pass_to_chld;
229ece520b1Sbluhm sigaction(SIGTERM, &sa, NULL);
230ece520b1Sbluhm sigaction(SIGHUP, &sa, NULL);
231ece520b1Sbluhm sigaction(SIGINT, &sa, NULL);
232ece520b1Sbluhm sigaction(SIGQUIT, &sa, NULL);
233ece520b1Sbluhm sa.sa_handler = sig_got_chld;
234ece520b1Sbluhm sa.sa_flags |= SA_NOCLDSTOP;
235ece520b1Sbluhm sigaction(SIGCHLD, &sa, NULL);
236ece520b1Sbluhm
237ece520b1Sbluhm setproctitle("[priv]");
238ed00ae78Sbluhm log_debug("[priv]: fork+exec done");
239ece520b1Sbluhm
240e733ee17Sbluhm sigemptyset(&sigmask);
241e733ee17Sbluhm if (sigprocmask(SIG_SETMASK, &sigmask, NULL) == -1)
242e733ee17Sbluhm err(1, "sigprocmask priv");
243e733ee17Sbluhm
244df69c215Sderaadt if (stat(conf, &cf_info) == -1)
24538182445Savsm err(1, "stat config file failed");
24638182445Savsm
24738182445Savsm TAILQ_INIT(&lognames);
24838182445Savsm increase_state(STATE_CONFIG);
249927e6d74Scanacar restart = 0;
25038182445Savsm
25138182445Savsm while (cur_state < STATE_QUIT) {
252ece520b1Sbluhm if (may_read(sock, &cmd, sizeof(int)))
253927e6d74Scanacar break;
25438182445Savsm switch (cmd) {
25538182445Savsm case PRIV_OPEN_TTY:
256ed00ae78Sbluhm log_debug("[priv]: msg PRIV_OPEN_TTY received");
257878b8a60Savsm /* Expecting: length, path */
258ece520b1Sbluhm must_read(sock, &path_len, sizeof(size_t));
259878b8a60Savsm if (path_len == 0 || path_len > sizeof(path))
260b2be3b7aSbluhm _exit(1);
261ece520b1Sbluhm must_read(sock, &path, path_len);
262878b8a60Savsm path[path_len - 1] = '\0';
26305b66061Sbluhm check_tty_name(path, sizeof(path));
264b7041c07Sderaadt fd = open(path, O_WRONLY|O_NONBLOCK);
265ece520b1Sbluhm send_fd(sock, fd);
266df69c215Sderaadt if (fd == -1)
2671451439bSavsm warnx("priv_open_tty failed");
268d0480564Savsm else
26938182445Savsm close(fd);
27038182445Savsm break;
27138182445Savsm
27238182445Savsm case PRIV_OPEN_LOG:
27347607485Shenning case PRIV_OPEN_PIPE:
274ed00ae78Sbluhm log_debug("[priv]: msg PRIV_OPEN_%s received",
27547607485Shenning cmd == PRIV_OPEN_PIPE ? "PIPE" : "LOG");
276878b8a60Savsm /* Expecting: length, path */
277ece520b1Sbluhm must_read(sock, &path_len, sizeof(size_t));
278878b8a60Savsm if (path_len == 0 || path_len > sizeof(path))
279b2be3b7aSbluhm _exit(1);
280ece520b1Sbluhm must_read(sock, &path, path_len);
281878b8a60Savsm path[path_len - 1] = '\0';
28205b66061Sbluhm check_log_name(path, sizeof(path));
28347607485Shenning
28447607485Shenning if (cmd == PRIV_OPEN_LOG)
28547607485Shenning fd = open_file(path);
28647607485Shenning else if (cmd == PRIV_OPEN_PIPE)
28747607485Shenning fd = open_pipe(path);
28847607485Shenning else
28947607485Shenning errx(1, "invalid cmd");
29047607485Shenning
291ece520b1Sbluhm send_fd(sock, fd);
292df69c215Sderaadt if (fd == -1)
2931451439bSavsm warnx("priv_open_log failed");
294d0480564Savsm else
29538182445Savsm close(fd);
29638182445Savsm break;
29738182445Savsm
29838182445Savsm case PRIV_OPEN_UTMP:
299ed00ae78Sbluhm log_debug("[priv]: msg PRIV_OPEN_UTMP received");
300b7041c07Sderaadt fd = open(_PATH_UTMP, O_RDONLY|O_NONBLOCK);
301ece520b1Sbluhm send_fd(sock, fd);
302df69c215Sderaadt if (fd == -1)
3031451439bSavsm warnx("priv_open_utmp failed");
304d0480564Savsm else
30538182445Savsm close(fd);
30638182445Savsm break;
30738182445Savsm
30838182445Savsm case PRIV_OPEN_CONFIG:
309ed00ae78Sbluhm log_debug("[priv]: msg PRIV_OPEN_CONFIG received");
310ece520b1Sbluhm stat(conf, &cf_info);
311b7041c07Sderaadt fd = open(conf, O_RDONLY|O_NONBLOCK);
312ece520b1Sbluhm send_fd(sock, fd);
313df69c215Sderaadt if (fd == -1)
3141451439bSavsm warnx("priv_open_config failed");
315d0480564Savsm else
31638182445Savsm close(fd);
31738182445Savsm break;
31838182445Savsm
31938182445Savsm case PRIV_CONFIG_MODIFIED:
320ed00ae78Sbluhm log_debug("[priv]: msg PRIV_CONFIG_MODIFIED received");
321df69c215Sderaadt if (stat(conf, &cf_stat) == -1 ||
32279b54589Sguenther timespeccmp(&cf_info.st_mtim,
32379b54589Sguenther &cf_stat.st_mtim, <) ||
32438182445Savsm cf_info.st_size != cf_stat.st_size) {
325ed00ae78Sbluhm log_debug("config file modified: restarting");
326927e6d74Scanacar restart = result = 1;
327ece520b1Sbluhm must_write(sock, &result, sizeof(int));
32838182445Savsm } else {
32938182445Savsm result = 0;
330ece520b1Sbluhm must_write(sock, &result, sizeof(int));
33138182445Savsm }
33238182445Savsm break;
33338182445Savsm
33438182445Savsm case PRIV_DONE_CONFIG_PARSE:
3358d92522dSderaadt if (pledge("stdio rpath wpath cpath dns sendfd id proc exec",
3368d92522dSderaadt NULL) == -1)
3378d92522dSderaadt err(1, "pledge done config");
338ed00ae78Sbluhm log_debug("[priv]: msg PRIV_DONE_CONFIG_PARSE "
339ed00ae78Sbluhm "received");
34038182445Savsm increase_state(STATE_RUNNING);
34138182445Savsm break;
34238182445Savsm
3432fa726a7Sbluhm case PRIV_GETADDRINFO:
344ed00ae78Sbluhm log_debug("[priv]: msg PRIV_GETADDRINFO received");
345ad087aa9Sbluhm /* Expecting: len, proto, len, host, len, serv */
346ece520b1Sbluhm must_read(sock, &protoname_len, sizeof(size_t));
347ad087aa9Sbluhm if (protoname_len == 0 ||
348ad087aa9Sbluhm protoname_len > sizeof(protoname))
349ad087aa9Sbluhm _exit(1);
350ece520b1Sbluhm must_read(sock, &protoname, protoname_len);
351ad087aa9Sbluhm protoname[protoname_len - 1] = '\0';
352ad087aa9Sbluhm
353ece520b1Sbluhm must_read(sock, &hostname_len, sizeof(size_t));
3542fa726a7Sbluhm if (hostname_len == 0 ||
3552fa726a7Sbluhm hostname_len > sizeof(hostname))
356b2be3b7aSbluhm _exit(1);
357ece520b1Sbluhm must_read(sock, &hostname, hostname_len);
358878b8a60Savsm hostname[hostname_len - 1] = '\0';
35997f3a029Sdjm
360ece520b1Sbluhm must_read(sock, &servname_len, sizeof(size_t));
3612fa726a7Sbluhm if (servname_len == 0 ||
3622fa726a7Sbluhm servname_len > sizeof(servname))
363b2be3b7aSbluhm _exit(1);
364ece520b1Sbluhm must_read(sock, &servname, servname_len);
36597f3a029Sdjm servname[servname_len - 1] = '\0';
36697f3a029Sdjm
367e470716cSbluhm memset(&hints, 0, sizeof(hints));
368cb08fbc7Sbluhm switch (strlen(protoname)) {
369cb08fbc7Sbluhm case 3:
370e470716cSbluhm hints.ai_family = AF_UNSPEC;
371cb08fbc7Sbluhm break;
372cb08fbc7Sbluhm case 4:
373cb08fbc7Sbluhm switch (protoname[3]) {
374cb08fbc7Sbluhm case '4':
375ad087aa9Sbluhm hints.ai_family = AF_INET;
376cb08fbc7Sbluhm break;
377cb08fbc7Sbluhm case '6':
378ad087aa9Sbluhm hints.ai_family = AF_INET6;
379cb08fbc7Sbluhm break;
380cb08fbc7Sbluhm default:
381cb08fbc7Sbluhm errx(1, "bad ip version %s", protoname);
382cb08fbc7Sbluhm }
383cb08fbc7Sbluhm break;
384cb08fbc7Sbluhm default:
385cb08fbc7Sbluhm errx(1, "bad protocol length %s", protoname);
386cb08fbc7Sbluhm }
387cb08fbc7Sbluhm if (strncmp(protoname, "udp", 3) == 0) {
388cb08fbc7Sbluhm hints.ai_socktype = SOCK_DGRAM;
389cb08fbc7Sbluhm hints.ai_protocol = IPPROTO_UDP;
390cb08fbc7Sbluhm } else if (strncmp(protoname, "tcp", 3) == 0) {
391cb08fbc7Sbluhm hints.ai_socktype = SOCK_STREAM;
392cb08fbc7Sbluhm hints.ai_protocol = IPPROTO_TCP;
393ad087aa9Sbluhm } else {
394ad087aa9Sbluhm errx(1, "unknown protocol %s", protoname);
395ad087aa9Sbluhm }
39697f3a029Sdjm i = getaddrinfo(hostname, servname, &hints, &res0);
39797f3a029Sdjm if (i != 0 || res0 == NULL) {
39838182445Savsm addr_len = 0;
399ece520b1Sbluhm must_write(sock, &addr_len, sizeof(int));
40038182445Savsm } else {
40197f3a029Sdjm /* Just send the first address */
40297f3a029Sdjm i = res0->ai_addrlen;
403ece520b1Sbluhm must_write(sock, &i, sizeof(int));
404ece520b1Sbluhm must_write(sock, res0->ai_addr, i);
40597f3a029Sdjm freeaddrinfo(res0);
40638182445Savsm }
40738182445Savsm break;
40838182445Savsm
409bd6df507Sbluhm case PRIV_GETNAMEINFO:
410ed00ae78Sbluhm log_debug("[priv]: msg PRIV_GETNAMEINFO received");
411ece520b1Sbluhm if (numeric)
412bd6df507Sbluhm errx(1, "rejected attempt to getnameinfo");
413bd6df507Sbluhm /* Expecting: length, sockaddr */
414ece520b1Sbluhm must_read(sock, &addr_len, sizeof(int));
41597e72a51Sbluhm if (addr_len <= 0 || (size_t)addr_len > sizeof(addr))
416b2be3b7aSbluhm _exit(1);
417ece520b1Sbluhm must_read(sock, &addr, addr_len);
418bd6df507Sbluhm if (getnameinfo((struct sockaddr *)&addr, addr_len,
419bd6df507Sbluhm hostname, sizeof(hostname), NULL, 0,
420bd6df507Sbluhm NI_NOFQDN|NI_NAMEREQD|NI_DGRAM) != 0) {
42138182445Savsm addr_len = 0;
422ece520b1Sbluhm must_write(sock, &addr_len, sizeof(int));
42338182445Savsm } else {
424bd6df507Sbluhm addr_len = strlen(hostname) + 1;
425ece520b1Sbluhm must_write(sock, &addr_len, sizeof(int));
426ece520b1Sbluhm must_write(sock, hostname, addr_len);
42738182445Savsm }
42838182445Savsm break;
42938182445Savsm default:
4301451439bSavsm errx(1, "unknown command %d", cmd);
43138182445Savsm break;
43238182445Savsm }
43338182445Savsm }
43438182445Savsm
435ece520b1Sbluhm close(sock);
4364dfa96bcSdjm
437927e6d74Scanacar if (restart) {
438ece520b1Sbluhm int status;
43938182445Savsm
440ece520b1Sbluhm waitpid(child_pid, &status, 0);
441e733ee17Sbluhm sigemptyset(&sigmask);
442e733ee17Sbluhm sigaddset(&sigmask, SIGHUP);
443e733ee17Sbluhm if (sigprocmask(SIG_SETMASK, &sigmask, NULL) == -1)
444e733ee17Sbluhm err(1, "sigprocmask exec");
44538182445Savsm execvp(argv[0], argv);
446ece520b1Sbluhm err(1, "exec restart '%s' failed", argv[0]);
44738182445Savsm }
4482f2a83bbSmoritz unlink(_PATH_LOGPID);
449ece520b1Sbluhm exit(0);
45038182445Savsm }
45138182445Savsm
45247607485Shenning static int
open_file(char * path)45347607485Shenning open_file(char *path)
45447607485Shenning {
45547607485Shenning /* must not start with | */
45647607485Shenning if (path[0] == '|')
45747607485Shenning return (-1);
45847607485Shenning
459b7041c07Sderaadt return (open(path, O_WRONLY|O_APPEND|O_NONBLOCK));
46047607485Shenning }
46147607485Shenning
46247607485Shenning static int
open_pipe(char * cmd)46347607485Shenning open_pipe(char *cmd)
46447607485Shenning {
46547607485Shenning char *argp[] = {"sh", "-c", NULL, NULL};
46647607485Shenning int fd[2];
46747607485Shenning int bsize, flags;
46847607485Shenning pid_t pid;
46947607485Shenning
47047607485Shenning /* skip over leading | and whitespace */
47147607485Shenning if (cmd[0] != '|')
47247607485Shenning return (-1);
47347607485Shenning for (cmd++; *cmd && *cmd == ' '; cmd++)
47447607485Shenning ; /* nothing */
47547607485Shenning if (!*cmd)
47647607485Shenning return (-1);
47747607485Shenning
47847607485Shenning argp[2] = cmd;
47947607485Shenning
48047607485Shenning if (socketpair(AF_UNIX, SOCK_STREAM, PF_UNSPEC, fd) == -1) {
48119f11518Sdjm warnx("open_pipe");
48247607485Shenning return (-1);
48347607485Shenning }
48447607485Shenning
48547607485Shenning /* make the fd on syslogd's side nonblocking */
4867bb95e13Skrw if ((flags = fcntl(fd[1], F_GETFL)) == -1) {
48719f11518Sdjm warnx("fcntl");
48847607485Shenning return (-1);
48947607485Shenning }
49047607485Shenning flags |= O_NONBLOCK;
49147607485Shenning if ((flags = fcntl(fd[1], F_SETFL, flags)) == -1) {
49219f11518Sdjm warnx("fcntl");
49347607485Shenning return (-1);
49447607485Shenning }
49547607485Shenning
49647607485Shenning switch (pid = fork()) {
49747607485Shenning case -1:
49819f11518Sdjm warnx("fork error");
49947607485Shenning return (-1);
50047607485Shenning case 0:
50147607485Shenning break;
50247607485Shenning default:
50347607485Shenning close(fd[0]);
50447607485Shenning return (fd[1]);
50547607485Shenning }
50647607485Shenning
50747607485Shenning close(fd[1]);
50847607485Shenning
50947607485Shenning /* grow receive buffer */
51047607485Shenning bsize = 65535;
51147607485Shenning while (bsize > 0 && setsockopt(fd[0], SOL_SOCKET, SO_RCVBUF,
51247607485Shenning &bsize, sizeof(bsize)) == -1)
51347607485Shenning bsize /= 2;
51447607485Shenning
51547607485Shenning if (setgroups(1, &pw->pw_gid) == -1 ||
51647607485Shenning setresgid(pw->pw_gid, pw->pw_gid, pw->pw_gid) == -1 ||
51747607485Shenning setresuid(pw->pw_uid, pw->pw_uid, pw->pw_uid) == -1)
51847607485Shenning err(1, "failure dropping privs");
51947607485Shenning
52047607485Shenning if (dup2(fd[0], STDIN_FILENO) == -1)
52147607485Shenning err(1, "dup2 failed");
5222338eccdSmartijn closefrom(STDERR_FILENO + 1);
52347607485Shenning if (execv("/bin/sh", argp) == -1)
52447607485Shenning err(1, "execv %s", cmd);
52519f11518Sdjm /* NOTREACHED */
52619f11518Sdjm return (-1);
52747607485Shenning }
52847607485Shenning
52938182445Savsm /* Check that the terminal device is ok, and if not, rewrite to /dev/null.
53038182445Savsm * Either /dev/console or /dev/tty* are allowed.
53138182445Savsm */
53238182445Savsm static void
check_tty_name(char * tty,size_t ttysize)53305b66061Sbluhm check_tty_name(char *tty, size_t ttysize)
53438182445Savsm {
53538182445Savsm const char ttypre[] = "/dev/tty";
53638182445Savsm char *p;
53738182445Savsm
53838182445Savsm /* Any path containing '..' is invalid. */
53905b66061Sbluhm for (p = tty; p + 1 < tty + ttysize && *p; p++)
54038182445Savsm if (*p == '.' && *(p + 1) == '.')
54138182445Savsm goto bad_path;
54238182445Savsm
54338182445Savsm if (strcmp(_PATH_CONSOLE, tty) && strncmp(tty, ttypre, strlen(ttypre)))
54438182445Savsm goto bad_path;
54538182445Savsm return;
54638182445Savsm
54738182445Savsm bad_path:
54838182445Savsm warnx ("%s: invalid attempt to open %s: rewriting to /dev/null",
549070b30bfSderaadt "check_tty_name", tty);
55005b66061Sbluhm strlcpy(tty, "/dev/null", ttysize);
55138182445Savsm }
55238182445Savsm
55338182445Savsm /* If we are in the initial configuration state, accept a logname and add
55438182445Savsm * it to the list of acceptable logfiles. Otherwise, check against this list
55538182445Savsm * and rewrite to /dev/null if it's a bad path.
55638182445Savsm */
55738182445Savsm static void
check_log_name(char * lognam,size_t logsize)55805b66061Sbluhm check_log_name(char *lognam, size_t logsize)
55938182445Savsm {
56038182445Savsm struct logname *lg;
56138182445Savsm char *p;
56238182445Savsm
56338182445Savsm /* Any path containing '..' is invalid. */
56405b66061Sbluhm for (p = lognam; p + 1 < lognam + logsize && *p; p++)
56538182445Savsm if (*p == '.' && *(p + 1) == '.')
56638182445Savsm goto bad_path;
56738182445Savsm
56838182445Savsm switch (cur_state) {
56938182445Savsm case STATE_CONFIG:
57038182445Savsm lg = malloc(sizeof(struct logname));
57138182445Savsm if (!lg)
57238182445Savsm err(1, "check_log_name() malloc");
573b9fc9a72Sderaadt strlcpy(lg->path, lognam, PATH_MAX);
57438182445Savsm TAILQ_INSERT_TAIL(&lognames, lg, next);
5758d92522dSderaadt if (lognam[0] != '|') {
5768d92522dSderaadt if (unveil(lognam, "w") == -1)
5778d92522dSderaadt goto bad_path;
5788d92522dSderaadt }
57938182445Savsm break;
58038182445Savsm case STATE_RUNNING:
58138182445Savsm TAILQ_FOREACH(lg, &lognames, next)
582a4570f53Sdjm if (!strcmp(lg->path, lognam))
58338182445Savsm return;
58438182445Savsm goto bad_path;
58538182445Savsm break;
58638182445Savsm default:
58738182445Savsm /* Any other state should just refuse the request */
58838182445Savsm goto bad_path;
58938182445Savsm break;
59038182445Savsm }
59138182445Savsm return;
59238182445Savsm
59338182445Savsm bad_path:
59438182445Savsm warnx("%s: invalid attempt to open %s: rewriting to /dev/null",
595070b30bfSderaadt "check_log_name", lognam);
59605b66061Sbluhm strlcpy(lognam, "/dev/null", logsize);
59738182445Savsm }
59838182445Savsm
59938182445Savsm /* Crank our state into less permissive modes */
60038182445Savsm static void
increase_state(int state)60138182445Savsm increase_state(int state)
60238182445Savsm {
60338182445Savsm if (state <= cur_state)
60438182445Savsm errx(1, "attempt to decrease or match current state");
605927e6d74Scanacar if (state < STATE_INIT || state > STATE_QUIT)
60638182445Savsm errx(1, "attempt to switch to invalid state");
60738182445Savsm cur_state = state;
60838182445Savsm }
60938182445Savsm
61038182445Savsm /* Open console or a terminal device for writing */
61138182445Savsm int
priv_open_tty(const char * tty)61238182445Savsm priv_open_tty(const char *tty)
61338182445Savsm {
614b9fc9a72Sderaadt char path[PATH_MAX];
61538182445Savsm int cmd, fd;
616878b8a60Savsm size_t path_len;
61738182445Savsm
61838182445Savsm if (priv_fd < 0)
61952c0045bSbluhm errx(1, "%s: called from privileged portion", __func__);
62038182445Savsm
62138182445Savsm if (strlcpy(path, tty, sizeof path) >= sizeof(path))
62238182445Savsm return -1;
623878b8a60Savsm path_len = strlen(path) + 1;
624878b8a60Savsm
62538182445Savsm cmd = PRIV_OPEN_TTY;
62638182445Savsm must_write(priv_fd, &cmd, sizeof(int));
627878b8a60Savsm must_write(priv_fd, &path_len, sizeof(size_t));
628878b8a60Savsm must_write(priv_fd, path, path_len);
62938182445Savsm fd = receive_fd(priv_fd);
63038182445Savsm return fd;
63138182445Savsm }
63238182445Savsm
63338182445Savsm /* Open log-file */
63438182445Savsm int
priv_open_log(const char * lognam)635a4570f53Sdjm priv_open_log(const char *lognam)
63638182445Savsm {
637b9fc9a72Sderaadt char path[PATH_MAX];
63838182445Savsm int cmd, fd;
639878b8a60Savsm size_t path_len;
64038182445Savsm
64138182445Savsm if (priv_fd < 0)
64252c0045bSbluhm errx(1, "%s: called from privileged child", __func__);
64338182445Savsm
644a4570f53Sdjm if (strlcpy(path, lognam, sizeof path) >= sizeof(path))
64538182445Savsm return -1;
646878b8a60Savsm path_len = strlen(path) + 1;
647878b8a60Savsm
64847607485Shenning if (lognam[0] == '|')
64947607485Shenning cmd = PRIV_OPEN_PIPE;
65047607485Shenning else
65138182445Savsm cmd = PRIV_OPEN_LOG;
65238182445Savsm must_write(priv_fd, &cmd, sizeof(int));
653878b8a60Savsm must_write(priv_fd, &path_len, sizeof(size_t));
654878b8a60Savsm must_write(priv_fd, path, path_len);
65538182445Savsm fd = receive_fd(priv_fd);
65638182445Savsm return fd;
65738182445Savsm }
65838182445Savsm
65938182445Savsm /* Open utmp for reading */
66038182445Savsm FILE *
priv_open_utmp(void)66138182445Savsm priv_open_utmp(void)
66238182445Savsm {
66338182445Savsm int cmd, fd;
66438182445Savsm FILE *fp;
66538182445Savsm
66638182445Savsm if (priv_fd < 0)
66752c0045bSbluhm errx(1, "%s: called from privileged portion", __func__);
66838182445Savsm
66938182445Savsm cmd = PRIV_OPEN_UTMP;
67038182445Savsm must_write(priv_fd, &cmd, sizeof(int));
67138182445Savsm fd = receive_fd(priv_fd);
67238182445Savsm if (fd < 0)
67338182445Savsm return NULL;
67438182445Savsm
67538182445Savsm fp = fdopen(fd, "r");
67638182445Savsm if (!fp) {
67738182445Savsm warn("priv_open_utmp: fdopen() failed");
67838182445Savsm close(fd);
67938182445Savsm return NULL;
68038182445Savsm }
68138182445Savsm
68238182445Savsm return fp;
68338182445Savsm }
68438182445Savsm
68538182445Savsm /* Open syslog config file for reading */
68638182445Savsm FILE *
priv_open_config(void)68738182445Savsm priv_open_config(void)
68838182445Savsm {
68938182445Savsm int cmd, fd;
69038182445Savsm FILE *fp;
69138182445Savsm
69238182445Savsm if (priv_fd < 0)
69352c0045bSbluhm errx(1, "%s: called from privileged portion", __func__);
69438182445Savsm
69538182445Savsm cmd = PRIV_OPEN_CONFIG;
69638182445Savsm must_write(priv_fd, &cmd, sizeof(int));
69738182445Savsm fd = receive_fd(priv_fd);
69838182445Savsm if (fd < 0)
69938182445Savsm return NULL;
70038182445Savsm
70138182445Savsm fp = fdopen(fd, "r");
70238182445Savsm if (!fp) {
70338182445Savsm warn("priv_open_config: fdopen() failed");
70438182445Savsm close(fd);
70538182445Savsm return NULL;
70638182445Savsm }
70738182445Savsm
70838182445Savsm return fp;
70938182445Savsm }
71038182445Savsm
71138182445Savsm /* Ask if config file has been modified since last attempt to read it */
71238182445Savsm int
priv_config_modified(void)7133dad9bb3Sderaadt priv_config_modified(void)
71438182445Savsm {
71538182445Savsm int cmd, res;
71638182445Savsm
71738182445Savsm if (priv_fd < 0)
71852c0045bSbluhm errx(1, "%s: called from privileged portion", __func__);
7191451439bSavsm
72038182445Savsm cmd = PRIV_CONFIG_MODIFIED;
72138182445Savsm must_write(priv_fd, &cmd, sizeof(int));
72238182445Savsm
72338182445Savsm /* Expect back integer signalling 1 for modification */
72438182445Savsm must_read(priv_fd, &res, sizeof(int));
72538182445Savsm return res;
72638182445Savsm }
72738182445Savsm
72838182445Savsm /* Child can signal that its initial parsing is done, so that parent
72938182445Savsm * can revoke further logfile permissions. This call only works once. */
73038182445Savsm void
priv_config_parse_done(void)73138182445Savsm priv_config_parse_done(void)
73238182445Savsm {
73338182445Savsm int cmd;
73438182445Savsm
73538182445Savsm if (priv_fd < 0)
73652c0045bSbluhm errx(1, "%s: called from privileged portion", __func__);
73738182445Savsm
73838182445Savsm cmd = PRIV_DONE_CONFIG_PARSE;
73938182445Savsm must_write(priv_fd, &cmd, sizeof(int));
74038182445Savsm }
74138182445Savsm
742bd6df507Sbluhm /* Name/service to address translation. Response is placed into addr.
743bd6df507Sbluhm * Return 0 for success or < 0 for error like getaddrinfo(3) */
74438182445Savsm int
priv_getaddrinfo(const char * proto,const char * host,const char * serv,struct sockaddr * addr,size_t addr_len)745*c1f8818cSbluhm priv_getaddrinfo(const char *proto, const char *host, const char *serv,
746*c1f8818cSbluhm struct sockaddr *addr, size_t addr_len)
74738182445Savsm {
74838bc45fcSbluhm char protocpy[5], hostcpy[NI_MAXHOST], servcpy[NI_MAXSERV];
74938182445Savsm int cmd, ret_len;
750ad087aa9Sbluhm size_t protoname_len, hostname_len, servname_len;
75138182445Savsm
75238182445Savsm if (priv_fd < 0)
75352c0045bSbluhm errx(1, "%s: called from privileged portion", __func__);
75438182445Savsm
755ad087aa9Sbluhm if (strlcpy(protocpy, proto, sizeof(protocpy)) >= sizeof(protocpy))
756ad087aa9Sbluhm errx(1, "%s: overflow attempt in protoname", __func__);
757ad087aa9Sbluhm protoname_len = strlen(protocpy) + 1;
758ad087aa9Sbluhm if (strlcpy(hostcpy, host, sizeof(hostcpy)) >= sizeof(hostcpy))
75952c0045bSbluhm errx(1, "%s: overflow attempt in hostname", __func__);
760878b8a60Savsm hostname_len = strlen(hostcpy) + 1;
761ad087aa9Sbluhm if (strlcpy(servcpy, serv, sizeof(servcpy)) >= sizeof(servcpy))
76252c0045bSbluhm errx(1, "%s: overflow attempt in servname", __func__);
76397f3a029Sdjm servname_len = strlen(servcpy) + 1;
764878b8a60Savsm
7652fa726a7Sbluhm cmd = PRIV_GETADDRINFO;
76638182445Savsm must_write(priv_fd, &cmd, sizeof(int));
767ad087aa9Sbluhm must_write(priv_fd, &protoname_len, sizeof(size_t));
768ad087aa9Sbluhm must_write(priv_fd, protocpy, protoname_len);
769878b8a60Savsm must_write(priv_fd, &hostname_len, sizeof(size_t));
770878b8a60Savsm must_write(priv_fd, hostcpy, hostname_len);
77197f3a029Sdjm must_write(priv_fd, &servname_len, sizeof(size_t));
77297f3a029Sdjm must_write(priv_fd, servcpy, servname_len);
77338182445Savsm
77438182445Savsm /* Expect back an integer size, and then a string of that length */
77538182445Savsm must_read(priv_fd, &ret_len, sizeof(int));
77638182445Savsm
77738182445Savsm /* Check there was no error (indicated by a return of 0) */
77838182445Savsm if (!ret_len)
7792fa726a7Sbluhm return (-1);
78038182445Savsm
78138182445Savsm /* Make sure we aren't overflowing the passed in buffer */
78297e72a51Sbluhm if (ret_len < 0 || (size_t)ret_len > addr_len)
78352c0045bSbluhm errx(1, "%s: overflow attempt in return", __func__);
78438182445Savsm
78538182445Savsm /* Read the resolved address and make sure we got all of it */
78697f3a029Sdjm memset(addr, '\0', addr_len);
78738182445Savsm must_read(priv_fd, addr, ret_len);
78897f3a029Sdjm
7892fa726a7Sbluhm return (0);
79038182445Savsm }
79138182445Savsm
792bd6df507Sbluhm /* Reverse address resolution; response is placed into host.
793bd6df507Sbluhm * Return 0 for success or < 0 for error like getnameinfo(3) */
79438182445Savsm int
priv_getnameinfo(struct sockaddr * sa,socklen_t salen,char * host,size_t hostlen)795bd6df507Sbluhm priv_getnameinfo(struct sockaddr *sa, socklen_t salen, char *host,
796bd6df507Sbluhm size_t hostlen)
79738182445Savsm {
79838182445Savsm int cmd, ret_len;
79938182445Savsm
80038182445Savsm if (priv_fd < 0)
80152c0045bSbluhm errx(1, "%s called from privileged portion", __func__);
80238182445Savsm
803bd6df507Sbluhm cmd = PRIV_GETNAMEINFO;
80438182445Savsm must_write(priv_fd, &cmd, sizeof(int));
805bd6df507Sbluhm must_write(priv_fd, &salen, sizeof(int));
806bd6df507Sbluhm must_write(priv_fd, sa, salen);
80738182445Savsm
80838182445Savsm /* Expect back an integer size, and then a string of that length */
80938182445Savsm must_read(priv_fd, &ret_len, sizeof(int));
81038182445Savsm
81138182445Savsm /* Check there was no error (indicated by a return of 0) */
8121451439bSavsm if (!ret_len)
813bd6df507Sbluhm return (-1);
81438182445Savsm
81538182445Savsm /* Check we don't overflow the passed in buffer */
81697e72a51Sbluhm if (ret_len < 0 || (size_t)ret_len > hostlen)
81752c0045bSbluhm errx(1, "%s: overflow attempt in return", __func__);
81838182445Savsm
81938182445Savsm /* Read the resolved hostname */
820bd6df507Sbluhm must_read(priv_fd, host, ret_len);
821bd6df507Sbluhm return (0);
82238182445Savsm }
82338182445Savsm
824365bac07Scanacar /* Pass the signal through to child */
82538182445Savsm static void
sig_pass_to_chld(int sig)82638182445Savsm sig_pass_to_chld(int sig)
82738182445Savsm {
8283d480636Sderaadt int save_errno = errno;
8291502b2edSotto
830927e6d74Scanacar if (child_pid != -1)
83138182445Savsm kill(child_pid, sig);
8323d480636Sderaadt errno = save_errno;
83338182445Savsm }
83438182445Savsm
83538182445Savsm /* When child dies, move into the shutdown state */
83638182445Savsm static void
sig_got_chld(int sig)83738182445Savsm sig_got_chld(int sig)
83838182445Savsm {
8393d480636Sderaadt int save_errno = errno;
84047607485Shenning pid_t pid;
84147607485Shenning
84247607485Shenning do {
84347607485Shenning pid = waitpid(WAIT_ANY, NULL, WNOHANG);
84414aef417Smpf if (pid == child_pid && cur_state < STATE_QUIT)
84538182445Savsm cur_state = STATE_QUIT;
84614aef417Smpf } while (pid > 0 || (pid == -1 && errno == EINTR));
8473d480636Sderaadt errno = save_errno;
84838182445Savsm }
84938182445Savsm
850927e6d74Scanacar /* Read all data or return 1 for error. */
851927e6d74Scanacar static int
may_read(int fd,void * buf,size_t n)852927e6d74Scanacar may_read(int fd, void *buf, size_t n)
853927e6d74Scanacar {
854927e6d74Scanacar char *s = buf;
85597e72a51Sbluhm ssize_t res;
85697e72a51Sbluhm size_t pos = 0;
857927e6d74Scanacar
858927e6d74Scanacar while (n > pos) {
859927e6d74Scanacar res = read(fd, s + pos, n - pos);
860927e6d74Scanacar switch (res) {
861927e6d74Scanacar case -1:
862927e6d74Scanacar if (errno == EINTR || errno == EAGAIN)
863927e6d74Scanacar continue;
864927e6d74Scanacar case 0:
865927e6d74Scanacar return (1);
866927e6d74Scanacar default:
867927e6d74Scanacar pos += res;
868927e6d74Scanacar }
869927e6d74Scanacar }
870927e6d74Scanacar return (0);
871927e6d74Scanacar }
872927e6d74Scanacar
87338182445Savsm /* Read data with the assertion that it all must come through, or
87438182445Savsm * else abort the process. Based on atomicio() from openssh. */
87538182445Savsm static void
must_read(int fd,void * buf,size_t n)87638182445Savsm must_read(int fd, void *buf, size_t n)
87738182445Savsm {
87838182445Savsm char *s = buf;
87997e72a51Sbluhm ssize_t res;
88097e72a51Sbluhm size_t pos = 0;
88138182445Savsm
88238182445Savsm while (n > pos) {
88338182445Savsm res = read(fd, s + pos, n - pos);
88438182445Savsm switch (res) {
88538182445Savsm case -1:
88638182445Savsm if (errno == EINTR || errno == EAGAIN)
88738182445Savsm continue;
88838182445Savsm case 0:
889b2be3b7aSbluhm _exit(1);
89038182445Savsm default:
89138182445Savsm pos += res;
89238182445Savsm }
89338182445Savsm }
89438182445Savsm }
89538182445Savsm
89638182445Savsm /* Write data with the assertion that it all has to be written, or
89738182445Savsm * else abort the process. Based on atomicio() from openssh. */
89838182445Savsm static void
must_write(int fd,void * buf,size_t n)89938182445Savsm must_write(int fd, void *buf, size_t n)
90038182445Savsm {
90138182445Savsm char *s = buf;
90297e72a51Sbluhm ssize_t res;
90397e72a51Sbluhm size_t pos = 0;
90438182445Savsm
90538182445Savsm while (n > pos) {
90638182445Savsm res = write(fd, s + pos, n - pos);
90738182445Savsm switch (res) {
90838182445Savsm case -1:
90938182445Savsm if (errno == EINTR || errno == EAGAIN)
91038182445Savsm continue;
91138182445Savsm case 0:
912b2be3b7aSbluhm _exit(1);
91338182445Savsm default:
91438182445Savsm pos += res;
91538182445Savsm }
91638182445Savsm }
91738182445Savsm }
918