summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--usr.sbin/syslogd/Makefile5
-rw-r--r--usr.sbin/syslogd/privsep.c632
-rw-r--r--usr.sbin/syslogd/privsep_fdpass.c120
-rw-r--r--usr.sbin/syslogd/syslogd.c184
-rw-r--r--usr.sbin/syslogd/syslogd.h45
-rw-r--r--usr.sbin/syslogd/ttymsg.c175
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);
+}