diff options
Diffstat (limited to 'usr.sbin')
-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); +} |