diff options
author | Anil Madhavapeddy <avsm@cvs.openbsd.org> | 2003-07-31 18:20:08 +0000 |
---|---|---|
committer | Anil Madhavapeddy <avsm@cvs.openbsd.org> | 2003-07-31 18:20:08 +0000 |
commit | 76f4db8655d7a4a42cd58874aba187d65de568d6 (patch) | |
tree | 56508a78ba8d34a01eade478cccb289a7155dbe2 | |
parent | 42a88e6e56997f2ae9854421641f80ac6aa802e6 (diff) |
Privilege separated syslog daemon. The child listening to log requests drops
to user _syslogd and chroots itself, while the privileged parent grants it
access to open logfiles and other calls it needs.
The only difference from existing behaviour is that if syslog.conf changes
and syslogd receives a HUP, it will re-exec itself and have two new PIDs.
A HUP with an unchanged config will make syslogd reopen logfiles as before.
Lots of help and code from deraadt@ , and advice from millert@
Various versions tested by todd, cloder, mpech, markus, tdeval and others
-rw-r--r-- | usr.sbin/syslogd/Makefile | 5 | ||||
-rw-r--r-- | usr.sbin/syslogd/privsep.c | 632 | ||||
-rw-r--r-- | usr.sbin/syslogd/privsep_fdpass.c | 120 | ||||
-rw-r--r-- | usr.sbin/syslogd/syslogd.c | 184 | ||||
-rw-r--r-- | usr.sbin/syslogd/syslogd.h | 45 | ||||
-rw-r--r-- | usr.sbin/syslogd/ttymsg.c | 175 |
6 files changed, 1094 insertions, 67 deletions
diff --git a/usr.sbin/syslogd/Makefile b/usr.sbin/syslogd/Makefile index 8c9d47e4278..2cefbabc26a 100644 --- a/usr.sbin/syslogd/Makefile +++ b/usr.sbin/syslogd/Makefile @@ -1,8 +1,7 @@ -# $OpenBSD: Makefile,v 1.3 1997/09/21 11:44:29 deraadt Exp $ +# $OpenBSD: Makefile,v 1.4 2003/07/31 18:20:07 avsm Exp $ PROG= syslogd -SRCS= syslogd.c ttymsg.c -.PATH: ${.CURDIR}/../../usr.bin/wall +SRCS= syslogd.c ttymsg.c privsep.c privsep_fdpass.c MAN= syslogd.8 syslog.conf.5 .include <bsd.prog.mk> diff --git a/usr.sbin/syslogd/privsep.c b/usr.sbin/syslogd/privsep.c new file mode 100644 index 00000000000..5a248eb61bc --- /dev/null +++ b/usr.sbin/syslogd/privsep.c @@ -0,0 +1,632 @@ +/* $OpenBSD: privsep.c,v 1.1 2003/07/31 18:20:07 avsm Exp $ */ + +/* + * Copyright (c) 2003 Anil Madhavapeddy <anil@recoil.org> + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ +#include <sys/ioctl.h> +#include <sys/param.h> +#include <sys/queue.h> +#include <sys/uio.h> +#include <sys/types.h> +#include <sys/socket.h> +#include <sys/stat.h> +#include <sys/wait.h> +#include <err.h> +#include <errno.h> +#include <fcntl.h> +#include <netdb.h> +#include <paths.h> +#include <pwd.h> +#include <signal.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> +#include <util.h> +#include <utmp.h> +#include "syslogd.h" + +/* + * syslogd can only go forward in these states; each state should represent + * less privilege. After STATE_INIT, the child is allowed to parse its + * config file once, and communicate the information regarding what logfiles + * it needs access to back to the parent. When that is done, it sends a + * message to the priv parent revoking this access, moving to STATE_RUNNING. + * In this state, any log-files not in the access list are rejected. + * + * This allows a HUP signal to the child to reopen its log files, and + * the config file to be parsed if it hasn't been changed (this is still + * useful to force resoluton of remote syslog servers again). + * If the config file has been modified, then the child dies, and + * the priv parent restarts itself. + */ +enum priv_state { + STATE_INIT, /* just started up */ + STATE_CONFIG, /* parsing config file for first time */ + STATE_RUNNING, /* running and accepting network traffic */ + STATE_QUIT, /* shutting down */ + STATE_RESTART, /* kill child and re-exec to restart */ +}; + +enum cmd_types { + PRIV_OPEN_TTY, /* open terminal or console device */ + PRIV_OPEN_LOG, /* open logfile for appending */ + PRIV_OPEN_UTMP, /* open utmp for reading only */ + PRIV_OPEN_CONFIG, /* open config file for reading only */ + PRIV_CONFIG_MODIFIED, /* check if config file has been modified */ + PRIV_GETHOSTBYNAME, /* resolve hostname into numerical address */ + PRIV_GETHOSTBYADDR, /* resolve numeric address into hostname */ + PRIV_DONE_CONFIG_PARSE, /* signal that the initial config parse is done */ +}; + +static int priv_fd = -1; +static pid_t child_pid; +static char config_file[MAXPATHLEN]; +static struct stat cf_info; +static int allow_gethostbyaddr = 0; +static volatile sig_atomic_t cur_state = STATE_INIT; + +/* Queue for the allowed logfiles */ +struct logname { + char path[MAXPATHLEN]; + TAILQ_ENTRY(logname) next; +}; +static TAILQ_HEAD(, logname) lognames; + +static void check_log_name(char *, size_t); +static void check_tty_name(char *, size_t); +static void increase_state(int); +static void sig_pass_to_chld(int); +static void sig_got_chld(int); +static void must_read(int, void *, size_t); +static void must_write(int, void *, size_t); + +int +priv_init(char *conf, int numeric, int lockfd, int nullfd, char *argv[]) +{ + int i, fd, socks[2], cmd, addr_len, addr_af, result; + char path[MAXPATHLEN], hostname[MAXHOSTNAMELEN]; + struct stat cf_stat; + struct hostent *hp; + struct passwd *pw; + + /* Create sockets */ + if (socketpair(AF_LOCAL, SOCK_STREAM, PF_UNSPEC, socks) == -1) + err(1, "socketpair() failed"); + + pw = getpwnam("_syslogd"); + if (pw == NULL) + errx(1, "unknown user _syslogd"); + + child_pid = fork(); + if (child_pid < 0) + err(1, "fork() failed"); + + if (!child_pid) { + /* Child - drop privileges and return */ + if (chroot(pw->pw_dir) != 0) + err(1, "unable to chroot"); + chdir("/"); + if (setegid(pw->pw_gid) == -1) + err(1, "setegid() failed"); + if (setgid(pw->pw_gid) == -1) + err(1, "setgid() failed"); + if (seteuid(pw->pw_uid) == -1) + err(1, "seteuid() failed"); + if (setuid(pw->pw_uid) == -1) + err(1, "setuid() failed"); + close(socks[0]); + priv_fd = socks[1]; + return 0; + } + + close(lockfd); + if (!Debug) { + dup2(nullfd, STDIN_FILENO); + dup2(nullfd, STDOUT_FILENO); + dup2(nullfd, STDERR_FILENO); + } + + if (nullfd > 2) + close(nullfd); + + /* Father */ + for (i = 1; i <= _NSIG; i++) + signal(i, SIG_DFL); + + /* Pass TERM/HUP through to child, and accept CHLD */ + signal(SIGTERM, sig_pass_to_chld); + signal(SIGHUP, sig_pass_to_chld); + signal(SIGCHLD, sig_got_chld); + + setproctitle("[priv]"); + close(socks[1]); + + /* Close descriptors that only the unpriv child needs */ + for (i = 0; i < nfunix; i++) + if (funix[i] != -1) + close(funix[i]); + if (finet != -1) + close(finet); + if (fklog != -1) + close(fklog); + + /* Save the config file specified by the child process */ + if (strlcpy(config_file, conf, sizeof config_file) >= sizeof(config_file)) + errx(1, "config_file truncation"); + + if (stat(config_file, &cf_info) < 0) + err(1, "stat config file failed"); + + /* Save whether or not the child can have access to gethostbyaddr(3) */ + if (numeric > 0) + allow_gethostbyaddr = 0; + else + allow_gethostbyaddr = 1; + + TAILQ_INIT(&lognames); + increase_state(STATE_CONFIG); + + while (cur_state < STATE_QUIT) { + must_read(socks[0], &cmd, sizeof(int)); + switch (cmd) { + case PRIV_OPEN_TTY: + must_read(socks[0], &path, sizeof path); + dprintf("[priv]: msg PRIV_OPEN_TTY received\n"); + check_tty_name(path, sizeof path); + fd = open(path, O_WRONLY|O_NONBLOCK, 0); + if (fd < 0) + warnx("%s: priv_open_tty failed", __func__); + send_fd(socks[0], fd); + close(fd); + break; + + case PRIV_OPEN_LOG: + must_read(socks[0], &path, sizeof path); + dprintf("[priv]: msg PRIV_OPEN_LOG received: %s\n", path); + check_log_name(path, sizeof path); + fd = open(path, O_WRONLY|O_APPEND|O_NONBLOCK, 0); + if (fd < 0) + warnx("%s: priv_open_log failed", __func__); + send_fd(socks[0], fd); + close(fd); + break; + + case PRIV_OPEN_UTMP: + dprintf("[priv]: msg PRIV_OPEN_UTMP received\n"); + fd = open(_PATH_UTMP, O_RDONLY|O_NONBLOCK, 0); + if (fd < 0) + warnx("%s: priv_open_utmp failed", __func__); + send_fd(socks[0], fd); + close(fd); + break; + + case PRIV_OPEN_CONFIG: + dprintf("[priv]: msg PRIV_OPEN_CONFIG received\n"); + stat(config_file, &cf_info); + fd = open(config_file, O_RDONLY|O_NONBLOCK, 0); + if (fd < 0) + warnx("%s: priv_open_config failed", __func__); + send_fd(socks[0], fd); + close(fd); + break; + + case PRIV_CONFIG_MODIFIED: + dprintf("[priv]: msg PRIV_CONFIG_MODIFIED received\n"); + if (stat(config_file, &cf_stat) < 0 || + timespeccmp(&cf_info.st_mtimespec, + &cf_stat.st_mtimespec, <) || + cf_info.st_size != cf_stat.st_size) { + dprintf("config file modified: restarting\n"); + result = 1; + must_write(socks[0], &result, sizeof(int)); + increase_state(STATE_RESTART); + } else { + result = 0; + must_write(socks[0], &result, sizeof(int)); + } + break; + + case PRIV_DONE_CONFIG_PARSE: + dprintf("[priv]: msg PRIV_DONE_CONFIG_PARSE received\n"); + increase_state(STATE_RUNNING); + break; + + case PRIV_GETHOSTBYNAME: + dprintf("[priv]: msg PRIV_GETHOSTBYNAME received\n"); + /* Expecting: hostname[MAXHOSTNAMELEN] */ + must_read(socks[0], &hostname, sizeof hostname); + hp = gethostbyname(hostname); + if (hp == NULL) { + addr_len = 0; + must_write(socks[0], &addr_len, sizeof(int)); + } else { + must_write(socks[0], &hp->h_length, sizeof(int)); + must_write(socks[0], hp->h_addr, hp->h_length); + } + break; + + case PRIV_GETHOSTBYADDR: + dprintf("[priv]: msg PRIV_GETHOSTBYADDR received\n"); + if (!allow_gethostbyaddr) + errx(1, "%s: rejected attempt to gethostbyaddr", + __func__); + /* Expecting: length, address, address family */ + must_read(socks[0], &addr_len, sizeof(int)); + if (addr_len > sizeof(hostname)) + _exit(0); + must_read(socks[0], hostname, addr_len); + must_read(socks[0], &addr_af, sizeof(int)); + hp = gethostbyaddr(hostname, addr_len, addr_af); + if (hp == NULL) { + addr_len = 0; + must_write(socks[0], &addr_len, sizeof(int)); + } else { + addr_len = strlen(hp->h_name) + 1; + must_write(socks[0], &addr_len, sizeof(int)); + must_write(socks[0], hp->h_name, addr_len); + } + break; + default: + errx(1, "%s: unknown command %d", __func__, cmd); + break; + } + } + + dprintf("%s: shutting down priv parent\n", __func__); + /* Unlink any domain sockets that have been opened */ + for (i = 0; i < nfunix; i++) + if (funixn[i] && funix[i] != -1) + (void)unlink(funixn[i]); + + if (cur_state == STATE_RESTART) { + int r; + + wait(&r); + execvp(argv[0], argv); + } + _exit(1); +} + +/* Check that the terminal device is ok, and if not, rewrite to /dev/null. + * Either /dev/console or /dev/tty* are allowed. + */ +static void +check_tty_name(char *tty, size_t ttylen) +{ + const char ttypre[] = "/dev/tty"; + char *p; + + /* Any path containing '..' is invalid. */ + for (p = tty; *p && (p - tty) < ttylen; p++) + if (*p == '.' && *(p + 1) == '.') + goto bad_path; + + if (strcmp(_PATH_CONSOLE, tty) && strncmp(tty, ttypre, strlen(ttypre))) + goto bad_path; + return; + +bad_path: + warnx ("%s: invalid attempt to open %s: rewriting to /dev/null", + __func__, tty); + strlcpy(tty, "/dev/null", ttylen); +} + +/* If we are in the initial configuration state, accept a logname and add + * it to the list of acceptable logfiles. Otherwise, check against this list + * and rewrite to /dev/null if it's a bad path. + */ +static void +check_log_name(char *log, size_t loglen) +{ + struct logname *lg; + char *p; + + /* Any path containing '..' is invalid. */ + for (p = log; *p && (p - log) < loglen; p++) + if (*p == '.' && *(p + 1) == '.') + goto bad_path; + + switch (cur_state) { + case STATE_CONFIG: + lg = malloc(sizeof(struct logname)); + if (!lg) + err(1, "check_log_name() malloc"); + strlcpy(lg->path, log, MAXPATHLEN); + TAILQ_INSERT_TAIL(&lognames, lg, next); + break; + case STATE_RUNNING: + TAILQ_FOREACH(lg, &lognames, next) + if (!strcmp(lg->path, log)) + return; + goto bad_path; + break; + default: + /* Any other state should just refuse the request */ + goto bad_path; + break; + } + return; + +bad_path: + warnx("%s: invalid attempt to open %s: rewriting to /dev/null", + __func__, log); + strlcpy(log, "/dev/null", loglen); +} + +/* Crank our state into less permissive modes */ +static void +increase_state(int state) +{ + if (state <= cur_state) + errx (1, "attempt to decrease or match current state"); + if (state < STATE_INIT || state > STATE_RESTART) + errx (1, "attempt to switch to invalid state"); + cur_state = state; +} + +/* Open console or a terminal device for writing */ +int +priv_open_tty(const char *tty) +{ + char path[MAXPATHLEN]; + int cmd, fd; + + dprintf("[unpriv] priv_open_tty\n"); + if (priv_fd < 0) + errx(1, "%s: called from privileged portion", __func__); + + if (strlcpy(path, tty, sizeof path) >= sizeof(path)) + return -1; + cmd = PRIV_OPEN_TTY; + must_write(priv_fd, &cmd, sizeof(int)); + must_write(priv_fd, path, sizeof(path)); + fd = receive_fd(priv_fd); + return fd; +} + +/* Open log-file */ +int +priv_open_log(const char *log) +{ + char path[MAXPATHLEN]; + int cmd, fd; + + dprintf("[unpriv] priv_open_log %s\n", log); + if (priv_fd < 0) + errx(1, "%s: called from privileged child\n", __func__); + + if (strlcpy(path, log, sizeof path) >= sizeof(path)) + return -1; + cmd = PRIV_OPEN_LOG; + must_write(priv_fd, &cmd, sizeof(int)); + must_write(priv_fd, path, sizeof(path)); + fd = receive_fd(priv_fd); + return fd; +} + +/* Open utmp for reading */ +FILE * +priv_open_utmp(void) +{ + int cmd, fd; + FILE *fp; + + dprintf("[unpriv] priv_open_utmp\n"); + if (priv_fd < 0) + errx(1, "%s: called from privileged portion", __func__); + + cmd = PRIV_OPEN_UTMP; + must_write(priv_fd, &cmd, sizeof(int)); + fd = receive_fd(priv_fd); + if (fd < 0) + return NULL; + + fp = fdopen(fd, "r"); + if (!fp) { + warn("priv_open_utmp: fdopen() failed"); + close (fd); + return NULL; + } + + return fp; +} + +/* Open syslog config file for reading */ +FILE * +priv_open_config(void) +{ + int cmd, fd; + FILE *fp; + + dprintf("[unpriv] priv_open_config\n"); + if (priv_fd < 0) + errx(1, "%s: called from privileged portion", __func__); + + cmd = PRIV_OPEN_CONFIG; + must_write(priv_fd, &cmd, sizeof(int)); + fd = receive_fd(priv_fd); + if (fd < 0) + return NULL; + + fp = fdopen(fd, "r"); + if (!fp) { + warn("priv_open_config: fdopen() failed"); + close (fd); + return NULL; + } + + return fp; +} + +/* Ask if config file has been modified since last attempt to read it */ +int +priv_config_modified() +{ + int cmd, res; + + dprintf("[unpriv] priv_config_modified called\n"); + if (priv_fd < 0) + errx(1, "%s: called from privileged portion", __func__); + cmd = PRIV_CONFIG_MODIFIED; + must_write(priv_fd, &cmd, sizeof(int)); + + /* Expect back integer signalling 1 for modification */ + must_read(priv_fd, &res, sizeof(int)); + return res; +} + +/* Child can signal that its initial parsing is done, so that parent + * can revoke further logfile permissions. This call only works once. */ +void +priv_config_parse_done(void) +{ + int cmd; + + if (priv_fd < 0) + errx(1, "%s: called from privileged portion", __func__); + + cmd = PRIV_DONE_CONFIG_PARSE; + must_write(priv_fd, &cmd, sizeof(int)); +} + +/* Resolve hostname into address. Response is placed into addr, and + * the length is returned (zero on error) */ +int +priv_gethostbyname(char *host, char *addr, size_t addr_len) +{ + char hostcpy[MAXHOSTNAMELEN]; + int cmd, ret_len; + + dprintf("[unpriv] %s called\n", __func__); + + if (strlcpy(hostcpy, host, sizeof hostcpy) >= sizeof(hostcpy)) + errx(1, "%s: overflow attempt in hostname", __func__); + + if (priv_fd < 0) + errx(1, "%s: called from privileged portion", __func__); + + cmd = PRIV_GETHOSTBYNAME; + must_write(priv_fd, &cmd, sizeof(int)); + must_write(priv_fd, hostcpy, sizeof(hostcpy)); + + /* Expect back an integer size, and then a string of that length */ + must_read(priv_fd, &ret_len, sizeof(int)); + + /* Check there was no error (indicated by a return of 0) */ + if (!ret_len) + return 0; + + /* Make sure we aren't overflowing the passed in buffer */ + if (addr_len < ret_len) + errx(1, "%s: overflow attempt in return", __func__); + + /* Read the resolved address and make sure we got all of it */ + must_read(priv_fd, addr, ret_len); + return ret_len; +} + +/* Reverse address resolution; response is placed into res, and length of + * response is returned (zero on error) */ +int +priv_gethostbyaddr(char *addr, int addr_len, int af, char *res, size_t res_len) +{ + int cmd, ret_len; + + dprintf("[unpriv] %s called\n", __func__); + + if (priv_fd < 0) + errx(1, "%s called from privileged portion", __func__); + + cmd = PRIV_GETHOSTBYADDR; + must_write(priv_fd, &cmd, sizeof(int)); + must_write(priv_fd, &addr_len, sizeof(int)); + must_write(priv_fd, addr, addr_len); + must_write(priv_fd, &af, sizeof(int)); + + /* Expect back an integer size, and then a string of that length */ + must_read(priv_fd, &ret_len, sizeof(int)); + + /* Check there was no error (indicated by a return of 0) */ + if (!res_len) + return 0; + + /* Check we don't overflow the passed in buffer */ + if (res_len < ret_len) + errx(1, "%s: overflow attempt in return", __func__); + + /* Read the resolved hostname */ + must_read(priv_fd, res, ret_len); + return ret_len; +} + +/* If priv parent gets a TERM or HUP, pass it through to child instead */ +static void +sig_pass_to_chld(int sig) +{ + kill(child_pid, sig); +} + +/* When child dies, move into the shutdown state */ +static void +sig_got_chld(int sig) +{ + if (cur_state < STATE_QUIT) + cur_state = STATE_QUIT; +} + +/* Read data with the assertion that it all must come through, or + * else abort the process. Based on atomicio() from openssh. */ +static void +must_read(int fd, void *buf, size_t n) +{ + char *s = buf; + ssize_t res, pos = 0; + + while (n > pos) { + res = read(fd, s + pos, n - pos); + switch (res) { + case -1: + if (errno == EINTR || errno == EAGAIN) + continue; + case 0: + _exit(0); + default: + pos += res; + } + } +} + +/* Write data with the assertion that it all has to be written, or + * else abort the process. Based on atomicio() from openssh. */ +static void +must_write(int fd, void *buf, size_t n) +{ + char *s = buf; + ssize_t res, pos = 0; + + while (n > pos) { + res = write(fd, s + pos, n - pos); + switch (res) { + case -1: + if (errno == EINTR || errno == EAGAIN) + continue; + case 0: + _exit(0); + default: + pos += res; + } + } +} diff --git a/usr.sbin/syslogd/privsep_fdpass.c b/usr.sbin/syslogd/privsep_fdpass.c new file mode 100644 index 00000000000..d0fd6d1b5e1 --- /dev/null +++ b/usr.sbin/syslogd/privsep_fdpass.c @@ -0,0 +1,120 @@ +/* $OpenBSD: privsep_fdpass.c,v 1.1 2003/07/31 18:20:07 avsm Exp $ */ + +/* + * Copyright 2001 Niels Provos <provos@citi.umich.edu> + * All rights reserved. + * + * Copyright (c) 2002 Matthieu Herrb + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * - Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following + * disclaimer in the documentation and/or other materials provided + * with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS + * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE + * COPYRIGHT HOLDERS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, + * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN + * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ +#include <sys/param.h> +#include <sys/uio.h> +#include <sys/types.h> +#include <sys/socket.h> +#include <sys/stat.h> +#include <err.h> +#include <errno.h> +#include <fcntl.h> +#include <signal.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> +#include "syslogd.h" + +void +send_fd(int socket, int fd) +{ + struct msghdr msg; + char tmp[CMSG_SPACE(sizeof(int))]; + struct cmsghdr *cmsg; + struct iovec vec; + int result = 0; + ssize_t n; + + memset(&msg, 0, sizeof(msg)); + + if (fd >= 0) { + msg.msg_control = (caddr_t)tmp; + msg.msg_controllen = CMSG_LEN(sizeof(int)); + cmsg = CMSG_FIRSTHDR(&msg); + cmsg->cmsg_len = CMSG_LEN(sizeof(int)); + cmsg->cmsg_level = SOL_SOCKET; + cmsg->cmsg_type = SCM_RIGHTS; + *(int *)CMSG_DATA(cmsg) = fd; + } else { + result = errno; + } + + vec.iov_base = &result; + vec.iov_len = sizeof(int); + msg.msg_iov = &vec; + msg.msg_iovlen = 1; + + if ((n = sendmsg(socket, &msg, 0)) == -1) + warn("%s: sendmsg(%d)", __func__, socket); + if (n != sizeof(int)) + warnx("%s: sendmsg: expected sent 1 got %ld", + __func__, (long)n); +} + +int +receive_fd(int socket) +{ + struct msghdr msg; + char tmp[CMSG_SPACE(sizeof(int))]; + struct cmsghdr *cmsg; + struct iovec vec; + ssize_t n; + int result; + int fd; + + memset(&msg, 0, sizeof(msg)); + vec.iov_base = &result; + vec.iov_len = sizeof(int); + msg.msg_iov = &vec; + msg.msg_iovlen = 1; + msg.msg_control = tmp; + msg.msg_controllen = sizeof(tmp); + + if ((n = recvmsg(socket, &msg, 0)) == -1) + warn("%s: recvmsg", __func__); + if (n != sizeof(int)) + warnx("%s: recvmsg: expected received 1 got %ld", + __func__, (long)n); + if (result == 0) { + cmsg = CMSG_FIRSTHDR(&msg); + if (cmsg->cmsg_type != SCM_RIGHTS) + warnx("%s: expected type %d got %d", __func__, + SCM_RIGHTS, cmsg->cmsg_type); + fd = (*(int *)CMSG_DATA(cmsg)); + return fd; + } else { + errno = result; + return -1; + } +} diff --git a/usr.sbin/syslogd/syslogd.c b/usr.sbin/syslogd/syslogd.c index 237ce3365b6..ae1c867b1d7 100644 --- a/usr.sbin/syslogd/syslogd.c +++ b/usr.sbin/syslogd/syslogd.c @@ -1,4 +1,4 @@ -/* $OpenBSD: syslogd.c,v 1.64 2003/07/08 01:28:11 avsm Exp $ */ +/* $OpenBSD: syslogd.c,v 1.65 2003/07/31 18:20:07 avsm Exp $ */ /* * Copyright (c) 1983, 1988, 1993, 1994 @@ -30,16 +30,16 @@ */ #ifndef lint -static char copyright[] = +static const char copyright[] = "@(#) Copyright (c) 1983, 1988, 1993, 1994\n\ The Regents of the University of California. All rights reserved.\n"; #endif /* not lint */ #ifndef lint #if 0 -static char sccsid[] = "@(#)syslogd.c 8.3 (Berkeley) 4/4/94"; +static const char sccsid[] = "@(#)syslogd.c 8.3 (Berkeley) 4/4/94"; #else -static char rcsid[] = "$OpenBSD: syslogd.c,v 1.64 2003/07/08 01:28:11 avsm Exp $"; +static const char rcsid[] = "$OpenBSD: syslogd.c,v 1.65 2003/07/31 18:20:07 avsm Exp $"; #endif #endif /* not lint */ @@ -104,14 +104,14 @@ static char rcsid[] = "$OpenBSD: syslogd.c,v 1.64 2003/07/08 01:28:11 avsm Exp $ #define SYSLOG_NAMES #include <sys/syslog.h> -char *ConfFile = _PATH_LOGCONF; -char *PidFile = _PATH_LOGPID; -char ctty[] = _PATH_CONSOLE; +#include "syslogd.h" -#define dprintf if (Debug) printf +char *ConfFile = _PATH_LOGCONF; +const char ctty[] = _PATH_CONSOLE; #define MAXUNAMES 20 /* maximum number of user names */ + /* * Flags to logmsg(). */ @@ -159,7 +159,7 @@ int repeatinterval[] = { 30, 120, 600 }; /* # of secs before flush */ #define MAXREPEAT ((sizeof(repeatinterval) / sizeof(repeatinterval[0])) - 1) #define REPEATTIME(f) ((f)->f_time + repeatinterval[(f)->f_repeatcount]) #define BACKOFF(f) { if (++(f)->f_repeatcount > MAXREPEAT) \ - (f)->f_repeatcount = MAXREPEAT; \ + (f)->f_repeatcount = MAXREPEAT; \ } /* values for f_type */ @@ -180,10 +180,12 @@ struct filed *Files; struct filed consfile; int Debug; /* debug flag */ +int Startup = 1; /* startup flag */ char LocalHostName[MAXHOSTNAMELEN]; /* our hostname */ char *LocalDomain; /* our local domain name */ int InetInuse = 0; /* non-zero if INET sockets are being used */ -int finet; /* Internet datagram socket */ +int finet = -1; /* Internet datagram socket */ +int fklog = -1; /* Kernel log device socket */ int LogPort; /* port number for INET connections */ int Initialized = 0; /* set when we have initialized ourselves */ @@ -197,8 +199,8 @@ volatile sig_atomic_t WantDie; volatile sig_atomic_t DoInit; void cfline(char *, struct filed *, char *); -char *cvthname(struct sockaddr_in *); -int decode(const char *, CODE *); +void cvthname(struct sockaddr_in *, char *, size_t); +int decode(const char *, const CODE *); void dodie(int); void doinit(int); void die(int); @@ -225,12 +227,14 @@ int funix[MAXFUNIX]; int main(int argc, char *argv[]) { - int ch, i, fklog, linesize, fdsrmax = 0; + int ch, i, linesize, fdsrmax = 0; struct sockaddr_un sunx, fromunix; struct sockaddr_in sin, frominet; socklen_t slen, len; fd_set *fdsr = NULL; char *p, *line; + char resolve[MAXHOSTNAMELEN]; + int lockpipe[2], nullfd = -1; FILE *fp; while ((ch = getopt(argc, argv, "dnuf:m:p:a:")) != -1) @@ -277,9 +281,7 @@ main(int argc, char *argv[]) if ((argc -= optind) != 0) usage(); - if (!Debug) - (void)daemon(0, 0); - else + if (Debug) setlinebuf(stdout); consfile.f_type = F_CONSOLE; @@ -298,14 +300,6 @@ main(int argc, char *argv[]) linesize++; line = malloc(linesize); - (void)signal(SIGHUP, doinit); - (void)signal(SIGTERM, dodie); - (void)signal(SIGINT, Debug ? dodie : SIG_IGN); - (void)signal(SIGQUIT, Debug ? dodie : SIG_IGN); - (void)signal(SIGCHLD, reapchild); - (void)signal(SIGALRM, domark); - (void)alarm(TIMERINTVL); - #ifndef SUN_LEN #define SUN_LEN(unp) (strlen((unp)->sun_path) + 2) #endif @@ -367,19 +361,71 @@ main(int argc, char *argv[]) if ((fklog = open(_PATH_KLOG, O_RDONLY, 0)) < 0) dprintf("can't open %s (%d)\n", _PATH_KLOG, errno); + dprintf("off & running....\n"); + + chdir("/"); + + if (!Debug) { + char c; + + pipe(lockpipe); + + switch(fork()) { + case -1: + exit(1); + case 0: + setsid(); + nullfd = open(_PATH_DEVNULL, O_RDWR); + close(lockpipe[0]); + break; + default: + close(lockpipe[1]); + read(lockpipe[0], &c, 1); + _exit(0); + } + } + /* tuck my process id away */ if (!Debug) { - fp = fopen(PidFile, "w"); + fp = fopen(_PATH_LOGPID, "w"); if (fp != NULL) { fprintf(fp, "%ld\n", (long)getpid()); (void) fclose(fp); } } - dprintf("off & running....\n"); + /* Privilege separation begins here */ + if (priv_init(ConfFile, NoDNS, lockpipe[1], nullfd, argv) < 0) + errx(1, "unable to privsep"); + /* Process is now unprivileged and inside a chroot */ init(); + Startup = 0; + + if (!Debug) { + dup2(nullfd, STDIN_FILENO); + dup2(nullfd, STDOUT_FILENO); + dup2(nullfd, STDERR_FILENO); + if (nullfd > 2) + close(nullfd); + close(lockpipe[1]); + } + + /* + * Signal to the priv process that the initial config parsing is done + * so that it will reject any future attempts to open more files + */ + priv_config_parse_done(); + + (void)signal(SIGHUP, doinit); + (void)signal(SIGTERM, dodie); + (void)signal(SIGINT, Debug ? dodie : SIG_IGN); + (void)signal(SIGQUIT, Debug ? dodie : SIG_IGN); + (void)signal(SIGCHLD, reapchild); + (void)signal(SIGALRM, domark); + (void)alarm(TIMERINTVL); + if (fklog != -1 && fklog > fdsrmax) fdsrmax = fklog; if (finet != -1 && finet > fdsrmax) @@ -445,7 +491,9 @@ main(int argc, char *argv[]) } else { if (i > 0) { line[i] = '\0'; - printline(cvthname(&frominet), line); + cvthname(&frominet, resolve, sizeof resolve); + dprintf("cvthname res: %s\n", resolve); + printline(resolve, line); } else if (i < 0 && errno != EINTR) logerror("recvfrom inet"); } @@ -504,7 +552,7 @@ printline(char *hname, char *msg) /* * Don't allow users to log kernel messages. * NOTE: since LOG_KERN == 0 this will also match - * messages with no facility specified. + * messages with no facility specified. */ if (LOG_FAC(pri) == LOG_KERN) pri = LOG_USER | LOG_PRI(pri); @@ -613,7 +661,7 @@ logmsg(int pri, char *msg, char *from, int flags) /* log the message to the particular outputs */ if (!Initialized) { f = &consfile; - f->f_file = open(ctty, O_WRONLY|O_NONBLOCK, 0); + f->f_file = priv_open_tty(ctty); if (f->f_file >= 0) { fprintlog(f, flags, msg); @@ -791,8 +839,7 @@ fprintlog(struct filed *f, int flags, char *msg) break; } else if ((e == EIO || e == EBADF) && f->f_type != F_FILE) { - f->f_file = open(f->f_un.f_fname, - O_WRONLY|O_APPEND|O_NONBLOCK, 0); + f->f_file = priv_open_tty(f->f_un.f_fname); if (f->f_file < 0) { f->f_type = F_UNUSED; logerror(f->f_un.f_fname); @@ -836,7 +883,7 @@ wallmsg(struct filed *f, struct iovec *iov) if (reenter++) return; - if ((uf = fopen(_PATH_UTMP, "r")) == NULL) { + if ((uf = priv_open_utmp()) == NULL) { logerror(_PATH_UTMP); reenter = 0; return; @@ -888,37 +935,39 @@ reapchild(int signo) /* * Return a printable representation of a host address. */ -char * -cvthname(struct sockaddr_in *f) +void +cvthname(struct sockaddr_in *f, char *result, size_t res_len) { - struct hostent *hp; sigset_t omask, nmask; - char *p; - char *ip; + char *p, *ip; + int ret_len; if (f->sin_family != AF_INET) { dprintf("Malformed from address\n"); - return ("???"); + strlcpy(result, "???", res_len); + return; } ip = inet_ntoa(f->sin_addr); dprintf("cvthname(%s)\n", ip); - if (NoDNS) - return (ip); + if (NoDNS) { + strlcpy(result, ip, res_len); + return; + } sigemptyset(&nmask); sigaddset(&nmask, SIGHUP); sigprocmask(SIG_BLOCK, &nmask, &omask); - hp = gethostbyaddr((char *)&f->sin_addr, - sizeof(struct in_addr), f->sin_family); + + ret_len = priv_gethostbyaddr((char *)&f->sin_addr, + sizeof(struct in_addr), f->sin_family, result, res_len); + sigprocmask(SIG_SETMASK, &omask, NULL); - if (hp == 0) { + if (ret_len == 0) { dprintf("Host name for your address (%s) unknown\n", ip); - return (ip); - } - if ((p = strchr(hp->h_name, '.')) && strcmp(p + 1, LocalDomain) == 0) + strlcpy(result, ip, res_len); + } else if ((p = strchr(result, '.')) && strcmp(p + 1, LocalDomain) == 0) *p = '\0'; - return (hp->h_name); } void @@ -954,7 +1003,10 @@ logerror(char *type) (void)snprintf(buf, sizeof(buf), "syslogd: %s", type); errno = 0; dprintf("%s\n", buf); - logmsg(LOG_SYSLOG|LOG_ERR, buf, LocalHostName, ADDDATE); + if (Startup) + fprintf(stderr, "%s\n", buf); + else + logmsg(LOG_SYSLOG|LOG_ERR, buf, LocalHostName, ADDDATE); } void @@ -963,7 +1015,6 @@ die(int signo) struct filed *f; int was_initialized = Initialized; char buf[100]; - int i; Initialized = 0; /* Don't log SIGCHLDs */ alarm(0); @@ -979,9 +1030,7 @@ die(int signo) errno = 0; logerror(buf); } - for (i = 0; i < nfunix; i++) - if (funixn[i] && funix[i] != -1) - (void)unlink(funixn[i]); + dprintf("[unpriv] syslogd child about to exit\n"); exit(0); } @@ -998,6 +1047,12 @@ init(void) dprintf("init\n"); + /* If config file has been modified, then just die to restart */ + if (priv_config_modified()) { + dprintf("config file changed: dying\n"); + die(0); + } + /* * Close all open log files. */ @@ -1025,7 +1080,7 @@ init(void) nextp = &Files; /* open the configuration file */ - if ((cf = fopen(ConfFile, "r")) == NULL) { + if ((cf = priv_open_config()) == NULL) { dprintf("cannot open %s\n", ConfFile); *nextp = (struct filed *)calloc(1, sizeof(*f)); cfline("*.ERR\t/dev/console", *nextp, "*"); @@ -1125,10 +1180,10 @@ init(void) void cfline(char *line, struct filed *f, char *prog) { - struct hostent *hp; - int i, pri; + int i, pri, addr_len; char *bp, *p, *q; char buf[MAXLINE], ebuf[100]; + char addr[MAXHOSTNAMELEN]; dprintf("cfline(\"%s\", f, \"%s\")\n", line, prog); @@ -1219,10 +1274,9 @@ cfline(char *line, struct filed *f, char *prog) break; (void)strlcpy(f->f_un.f_forw.f_hname, ++p, sizeof(f->f_un.f_forw.f_hname)); - hp = gethostbyname(f->f_un.f_forw.f_hname); - if (hp == NULL) { - extern int h_errno; - + addr_len = priv_gethostbyname(f->f_un.f_forw.f_hname, + addr, sizeof addr); + if (addr_len < 1) { logerror((char *)hstrerror(h_errno)); break; } @@ -1231,14 +1285,16 @@ cfline(char *line, struct filed *f, char *prog) f->f_un.f_forw.f_addr.sin_len = sizeof(f->f_un.f_forw.f_addr); f->f_un.f_forw.f_addr.sin_family = AF_INET; f->f_un.f_forw.f_addr.sin_port = LogPort; - memmove(&f->f_un.f_forw.f_addr.sin_addr, hp->h_addr, - hp->h_length); + memmove(&f->f_un.f_forw.f_addr.sin_addr, addr, addr_len); f->f_type = F_FORW; break; case '/': (void)strlcpy(f->f_un.f_fname, p, sizeof(f->f_un.f_fname)); - f->f_file = open(p, O_WRONLY|O_APPEND|O_NONBLOCK, 0); + if (strcmp(p, ctty) == 0) + f->f_file = priv_open_tty(p); + else + f->f_file = priv_open_log(p); if (f->f_file < 0) { f->f_type = F_UNUSED; logerror(p); @@ -1305,9 +1361,9 @@ getmsgbufsize(void) * Decode a symbolic name to a numeric value */ int -decode(const char *name, CODE *codetab) +decode(const char *name, const CODE *codetab) { - CODE *c; + const CODE *c; char *p, buf[40]; if (isdigit(*name)) diff --git a/usr.sbin/syslogd/syslogd.h b/usr.sbin/syslogd/syslogd.h new file mode 100644 index 00000000000..567ac8019e6 --- /dev/null +++ b/usr.sbin/syslogd/syslogd.h @@ -0,0 +1,45 @@ +/* + * Copyright (c) 2003 Anil Madhavapeddy <anil@recoil.org> + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +/* Privilege separation */ +int priv_init(char *, int, int, int, char **); +int priv_open_tty(const char *); +int priv_open_log(const char *); +FILE *priv_open_utmp(void); +FILE *priv_open_config(void); +void priv_config_parse_done(void); +int priv_config_modified(void); +int priv_gethostbyname(char *, char *, size_t); +int priv_gethostbyaddr(char *, int, int, char *, size_t); + +/* Terminal message */ +char *ttymsg(struct iovec *, int, char *, int); + +/* File descriptor send/recv */ +void send_fd(int, int); +int receive_fd(int); + +/* The list of domain sockets */ +#define MAXFUNIX 21 +extern int nfunix; +extern char *funixn[MAXFUNIX]; +extern int funix[MAXFUNIX]; +extern int finet; +extern int fklog; + +#define dprintf if (Debug) printf +extern int Debug; +extern int Startup; diff --git a/usr.sbin/syslogd/ttymsg.c b/usr.sbin/syslogd/ttymsg.c new file mode 100644 index 00000000000..3ef189c07d0 --- /dev/null +++ b/usr.sbin/syslogd/ttymsg.c @@ -0,0 +1,175 @@ +/* $OpenBSD: ttymsg.c,v 1.1 2003/07/31 18:20:07 avsm Exp $ */ +/* $NetBSD: ttymsg.c,v 1.3 1994/11/17 07:17:55 jtc Exp $ */ + +/* + * Copyright (c) 1989, 1993 + * The Regents of the University of California. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the University nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#ifndef lint +#if 0 +static const char sccsid[] = "@(#)ttymsg.c 8.2 (Berkeley) 11/16/93"; +#endif +static const char rcsid[] = "$OpenBSD: ttymsg.c,v 1.1 2003/07/31 18:20:07 avsm Exp $"; +#endif /* not lint */ + +#include <sys/types.h> +#include <sys/uio.h> +#include <signal.h> +#include <fcntl.h> +#include <dirent.h> +#include <errno.h> +#include <paths.h> +#include <unistd.h> +#include <stdio.h> +#include <string.h> +#include <stdlib.h> +#include <sys/stat.h> + +#include "syslogd.h" + +/* + * Display the contents of a uio structure on a terminal. + * Forks and finishes in child if write would block, waiting up to tmout + * seconds. Returns pointer to error string on unexpected error; + * string is not newline-terminated. Various "normal" errors are ignored + * (exclusive-use, lack of permission, etc.). + */ +char * +ttymsg(struct iovec *iov, int iovcnt, char *line, int tmout) +{ + static char device[MAXNAMLEN] = _PATH_DEV; + static char errbuf[1024]; + int cnt, fd, left, wret; + struct iovec localiov[6]; + int forked = 0; + sigset_t mask; + + if (iovcnt > sizeof(localiov) / sizeof(localiov[0])) + return ("too many iov's (change code in wall/ttymsg.c)"); + + /* + * Ignore lines that start with "ftp" or "uucp". + */ + if ((strncmp(line, "ftp", 3) == 0) || + (strncmp(line, "uucp", 4) == 0)) + return (NULL); + + (void) strlcpy(device + sizeof(_PATH_DEV) - 1, line, + sizeof(device) - (sizeof(_PATH_DEV) - 1)); + if (strchr(device + sizeof(_PATH_DEV) - 1, '/')) { + /* A slash is an attempt to break security... */ + (void) snprintf(errbuf, sizeof(errbuf), "'/' in \"%s\"", + device); + return (errbuf); + } + + /* + * open will fail on slip lines or exclusive-use lines + * if not running as root; not an error. + */ + if ((fd = priv_open_tty(device)) < 0) { + if (errno == EBUSY || errno == EACCES) + return (NULL); + (void) snprintf(errbuf, sizeof(errbuf), + "%s: %s", device, strerror(errno)); + return (errbuf); + } + + for (cnt = left = 0; cnt < iovcnt; ++cnt) + left += iov[cnt].iov_len; + + for (;;) { + wret = writev(fd, iov, iovcnt); + if (wret >= left) + break; + if (wret >= 0) { + left -= wret; + if (iov != localiov) { + bcopy(iov, localiov, + iovcnt * sizeof(struct iovec)); + iov = localiov; + } + for (cnt = 0; wret >= iov->iov_len; ++cnt) { + wret -= iov->iov_len; + ++iov; + --iovcnt; + } + if (wret) { + iov->iov_base += wret; + iov->iov_len -= wret; + } + continue; + } + if (errno == EWOULDBLOCK) { + int off = 0; + pid_t cpid; + + if (forked) { + (void) close(fd); + _exit(1); + } + cpid = fork(); + if (cpid < 0) { + (void) snprintf(errbuf, sizeof(errbuf), + "fork: %s", strerror(errno)); + (void) close(fd); + return (errbuf); + } + if (cpid) { /* parent */ + (void) close(fd); + return (NULL); + } + forked++; + /* wait at most tmout seconds */ + (void) signal(SIGALRM, SIG_DFL); + (void) signal(SIGTERM, SIG_DFL); /* XXX */ + (void) sigemptyset(&mask); + (void) sigprocmask(SIG_SETMASK, &mask, NULL); + (void) alarm((u_int)tmout); + (void) fcntl(fd, O_NONBLOCK, &off); + continue; + } + /* + * We get ENODEV on a slip line if we're running as root, + * and EIO if the line just went away. + */ + if (errno == ENODEV || errno == EIO) + break; + (void) close(fd); + if (forked) + _exit(1); + (void) snprintf(errbuf, sizeof(errbuf), + "%s: %s", device, strerror(errno)); + return (errbuf); + } + + (void) close(fd); + if (forked) + _exit(0); + return (NULL); +} |