OpenBSD-4.6/sbin/isakmpd/monitor.c

Compare this file to the similar file:
Show the results in this format:

/* $OpenBSD: monitor.c,v 1.71 2007/08/11 00:20:30 hshoexer Exp $	 */

/*
 * Copyright (c) 2003 Håkan Olsson.  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.
 *
 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``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 AUTHOR 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/types.h>
#include <sys/socket.h>
#include <sys/ioctl.h>
#include <sys/stat.h>
#include <sys/wait.h>
#include <netinet/in.h>

#include <dirent.h>
#include <errno.h>
#include <fcntl.h>
#include <pwd.h>
#include <signal.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>

#include <regex.h>
#include <keynote.h>

#include "conf.h"
#include "log.h"
#include "monitor.h"
#include "policy.h"
#include "ui.h"
#include "util.h"
#include "pf_key_v2.h"

struct monitor_state {
	pid_t           pid;
	int             s;
	char            root[MAXPATHLEN];
} m_state;

extern char *pid_file;

extern void	set_slave_signals(void);

/* Private functions.  */
static void	must_read(void *, size_t);
static void	must_write(const void *, size_t);

static void	m_priv_getfd(void);
static void	m_priv_setsockopt(void);
static void	m_priv_req_readdir(void);
static void	m_priv_bind(void);
static void	m_priv_pfkey_open(void);
static int	m_priv_local_sanitize_path(char *, size_t, int);
static int	m_priv_check_sockopt(int, int);
static int	m_priv_check_bind(const struct sockaddr *, socklen_t);

static void	set_monitor_signals(void);
static void	sig_pass_to_chld(int);

/*
 * Public functions, unprivileged.
 */

/* Setup monitor context, fork, drop child privs.  */
pid_t
monitor_init(int debug)
{
	struct passwd  *pw;
	int             p[2];

	bzero(&m_state, sizeof m_state);

	if (socketpair(AF_UNIX, SOCK_STREAM, PF_UNSPEC, p) != 0)
		log_fatal("monitor_init: socketpair() failed");

	pw = getpwnam(ISAKMPD_PRIVSEP_USER);
	if (pw == NULL)
		log_fatalx("monitor_init: getpwnam(\"%s\") failed",
		    ISAKMPD_PRIVSEP_USER);
	strlcpy(m_state.root, pw->pw_dir, sizeof m_state.root);

	set_monitor_signals();
	m_state.pid = fork();

	if (m_state.pid == -1)
		log_fatal("monitor_init: fork of unprivileged child failed");
	if (m_state.pid == 0) {
		/* The child process drops privileges. */
		set_slave_signals();

		if (chroot(pw->pw_dir) != 0 || chdir("/") != 0)
			log_fatal("monitor_init: chroot failed");

		if (setgroups(1, &pw->pw_gid) == -1 ||
		    setresgid(pw->pw_gid, pw->pw_gid, pw->pw_gid) ||
		    setresuid(pw->pw_uid, pw->pw_uid, pw->pw_uid))
			log_fatal("monitor_init: can't drop privileges");

		m_state.s = p[0];
		close(p[1]);

		LOG_DBG((LOG_MISC, 10,
		    "monitor_init: privileges dropped for child process"));
	} else {
		/* Privileged monitor. */
		setproctitle("monitor [priv]");

		m_state.s = p[1];
		close(p[0]);
	}

	/* With "-dd", stop and wait here. For gdb "attach" etc.  */
	if (debug > 1) {
		log_print("monitor_init: stopped %s PID %d fd %d%s",
		    m_state.pid ? "priv" : "child", getpid(), m_state.s,
		    m_state.pid ? ", waiting for SIGCONT" : "");
		kill(getpid(), SIGSTOP);	/* Wait here for SIGCONT.  */
		if (m_state.pid)
			kill(m_state.pid, SIGCONT); /* Continue child.  */
	}

	return m_state.pid;
}

void
monitor_exit(int code)
{
	int status;
	pid_t pid;

	if (m_state.pid != 0) {
		/* When called from the monitor, kill slave and wait for it  */
		kill(m_state.pid, SIGTERM);

		do {
			pid = waitpid(m_state.pid, &status, 0);
		} while (pid == -1 && errno == EINTR);

		/* Remove FIFO and pid files.  */
		unlink(ui_fifo);
		unlink(pid_file);
	}

	close(m_state.s);
	exit(code);
}

int
monitor_pf_key_v2_open(void)
{
	int	err, cmd;

	cmd = MONITOR_PFKEY_OPEN;
	must_write(&cmd, sizeof cmd);

	must_read(&err, sizeof err);
	if (err < 0) {
		log_error("monitor_pf_key_v2_open: parent could not create "
		    "PF_KEY socket");
		return -1;
	}
	pf_key_v2_socket = mm_receive_fd(m_state.s);
	if (pf_key_v2_socket < 0) {
		log_error("monitor_pf_key_v2_open: mm_receive_fd() failed");
		return -1;
	}

	return pf_key_v2_socket;
}

int
monitor_open(const char *path, int flags, mode_t mode)
{
	size_t	len;
	int	fd, err, cmd;
	char	pathreal[MAXPATHLEN];

	if (path[0] == '/')
		strlcpy(pathreal, path, sizeof pathreal);
	else
		snprintf(pathreal, sizeof pathreal, "%s/%s", m_state.root,
		    path);

	cmd = MONITOR_GET_FD;
	must_write(&cmd, sizeof cmd);

	len = strlen(pathreal);
	must_write(&len, sizeof len);
	must_write(&pathreal, len);

	must_write(&flags, sizeof flags);
	must_write(&mode, sizeof mode);

	must_read(&err, sizeof err);
	if (err != 0) {
		errno = err;
		return -1;
	}

	fd = mm_receive_fd(m_state.s);
	if (fd < 0) {
		log_error("monitor_open: mm_receive_fd () failed");
		return -1;
	}

	return fd;
}

FILE *
monitor_fopen(const char *path, const char *mode)
{
	FILE	*fp;
	int	 fd, flags = 0, saved_errno;
	mode_t	 mask, cur_umask;

	/* Only the child process is supposed to run this.  */
	if (m_state.pid)
		log_fatal("[priv] bad call to monitor_fopen");

	switch (mode[0]) {
	case 'r':
		flags = (mode[1] == '+' ? O_RDWR : O_RDONLY);
		break;
	case 'w':
		flags = (mode[1] == '+' ? O_RDWR : O_WRONLY) | O_CREAT |
		    O_TRUNC;
		break;
	case 'a':
		flags = (mode[1] == '+' ? O_RDWR : O_WRONLY) | O_CREAT |
		    O_APPEND;
		break;
	default:
		log_fatal("monitor_fopen: bad call");
	}

	cur_umask = umask(0);
	(void)umask(cur_umask);
	mask = S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH | S_IWOTH;
	mask &= ~cur_umask;

	fd = monitor_open(path, flags, mask);
	if (fd < 0)
		return NULL;

	/* Got the fd, attach a FILE * to it.  */
	fp = fdopen(fd, mode);
	if (!fp) {
		log_error("monitor_fopen: fdopen() failed");
		saved_errno = errno;
		close(fd);
		errno = saved_errno;
		return NULL;
	}
	return fp;
}

int
monitor_stat(const char *path, struct stat *sb)
{
	int	fd, r, saved_errno;

	/* O_NONBLOCK is needed for stat'ing fifos. */
	fd = monitor_open(path, O_RDONLY | O_NONBLOCK, 0);
	if (fd < 0)
		return -1;

	r = fstat(fd, sb);
	saved_errno = errno;
	close(fd);
	errno = saved_errno;
	return r;
}

int
monitor_setsockopt(int s, int level, int optname, const void *optval,
    socklen_t optlen)
{
	int	ret, err, cmd;

	cmd = MONITOR_SETSOCKOPT;
	must_write(&cmd, sizeof cmd);
	if (mm_send_fd(m_state.s, s)) {
		log_print("monitor_setsockopt: read/write error");
		return -1;
	}

	must_write(&level, sizeof level);
	must_write(&optname, sizeof optname);
	must_write(&optlen, sizeof optlen);
	must_write(optval, optlen);

	must_read(&err, sizeof err);
	must_read(&ret, sizeof ret);
	if (err != 0)
		errno = err;
	return ret;
}

int
monitor_bind(int s, const struct sockaddr *name, socklen_t namelen)
{
	int	ret, err, cmd;

	cmd = MONITOR_BIND;
	must_write(&cmd, sizeof cmd);
	if (mm_send_fd(m_state.s, s)) {
		log_print("monitor_bind: read/write error");
		return -1;
	}

	must_write(&namelen, sizeof namelen);
	must_write(name, namelen);

	must_read(&err, sizeof err);
	must_read(&ret, sizeof ret);
	if (err != 0)
		errno = err;
	return ret;
}

int
monitor_req_readdir(const char *filename)
{
	int cmd, err;
	size_t len;

	cmd = MONITOR_REQ_READDIR;
	must_write(&cmd, sizeof cmd);

	len = strlen(filename);
	must_write(&len, sizeof len);
	must_write(filename, len);

	must_read(&err, sizeof err);
	if (err == -1)
		must_read(&errno, sizeof errno);

	return err;
}

int
monitor_readdir(char *file, size_t size)
{
	int fd;
	size_t len;

	must_read(&len, sizeof len);
	if (len == 0)
		return -1;
	if (len >= size)
		log_fatal("monitor_readdir: received bad length from monitor");
	must_read(file, len);
	file[len] = '\0';
	fd = mm_receive_fd(m_state.s);
	return fd;
}

void
monitor_init_done(void)
{
	int	cmd;

	cmd = MONITOR_INIT_DONE;
	must_write(&cmd, sizeof cmd);
}

/*
 * Start of code running with privileges (the monitor process).
 */

static void
set_monitor_signals(void)
{
	int n;

	for (n = 1; n < _NSIG; n++)
		signal(n, SIG_DFL);

	/* Forward some signals to the child. */
	signal(SIGTERM, sig_pass_to_chld);
	signal(SIGHUP, sig_pass_to_chld);
	signal(SIGUSR1, sig_pass_to_chld);
}

static void
sig_pass_to_chld(int sig)
{
	int	oerrno = errno;

	if (m_state.pid > 0)
		kill(m_state.pid, sig);
	errno = oerrno;
}

/* This function is where the privileged process waits(loops) indefinitely.  */
void
monitor_loop(int debug)
{
	int	 msgcode;

	if (!debug)
		log_to(0);

	for (;;) {
		must_read(&msgcode, sizeof msgcode);

		switch (msgcode) {
		case MONITOR_GET_FD:
			m_priv_getfd();
			break;

		case MONITOR_PFKEY_OPEN:
			LOG_DBG((LOG_MISC, 80,
			    "monitor_loop: MONITOR_PFKEY_OPEN"));
			m_priv_pfkey_open();
			break;

		case MONITOR_SETSOCKOPT:
			LOG_DBG((LOG_MISC, 80,
			    "monitor_loop: MONITOR_SETSOCKOPT"));
			m_priv_setsockopt();
			break;

		case MONITOR_BIND:
			LOG_DBG((LOG_MISC, 80,
			    "monitor_loop: MONITOR_BIND"));
			m_priv_bind();
			break;

		case MONITOR_REQ_READDIR:
			LOG_DBG((LOG_MISC, 80,
			    "monitor_loop: MONITOR_REQ_READDIR"));
			m_priv_req_readdir();
			break;

		case MONITOR_INIT_DONE:
			LOG_DBG((LOG_MISC, 80,
			    "monitor_loop: MONITOR_INIT_DONE"));
			break;

		case MONITOR_SHUTDOWN:
			LOG_DBG((LOG_MISC, 80,
			    "monitor_loop: MONITOR_SHUTDOWN"));
			break;

		default:
			log_print("monitor_loop: got unknown code %d",
			    msgcode);
		}
	}

	exit(0);
}


/* Privileged: called by monitor_loop.  */
static void
m_priv_pfkey_open(void)
{
	int	fd, err = 0;

	fd = pf_key_v2_open();
	if (fd < 0)
		err = -1;

	must_write(&err, sizeof err);

	if (fd > 0 && mm_send_fd(m_state.s, fd)) {
		log_error("m_priv_pfkey_open: read/write operation failed");
		close(fd);
		return;
	}
	close(fd);
}

/* Privileged: called by monitor_loop.  */
static void
m_priv_getfd(void)
{
	char	path[MAXPATHLEN];
	size_t	len;
	int	v, flags, ret;
	int	err = 0;
	mode_t	mode;

	must_read(&len, sizeof len);
	if (len == 0 || len >= sizeof path)
		log_fatal("m_priv_getfd: invalid pathname length");

	must_read(path, len);
	path[len] = '\0';
	if (strlen(path) != len)
		log_fatal("m_priv_getfd: invalid pathname");

	must_read(&flags, sizeof flags);
	must_read(&mode, sizeof mode);

	if ((ret = m_priv_local_sanitize_path(path, sizeof path, flags))
	    != 0) {
		if (ret == 1)
			log_print("m_priv_getfd: illegal path \"%s\"", path);
		err = EACCES;
		v = -1;
	} else {
		if ((v = open(path, flags, mode)) == -1)
			err = errno;
	}

	must_write(&err, sizeof err);

	if (v != -1) {
		if (mm_send_fd(m_state.s, v) == -1)
			log_error("m_priv_getfd: sending fd failed");
		close(v);
	}
}

/* Privileged: called by monitor_loop.  */
static void
m_priv_setsockopt(void)
{
	int		 sock, level, optname, v;
	int		 err = 0;
	char		*optval = 0;
	socklen_t	 optlen;

	sock = mm_receive_fd(m_state.s);
	if (sock < 0) {
		log_print("m_priv_setsockopt: read/write error");
		return;
	}

	must_read(&level, sizeof level);
	must_read(&optname, sizeof optname);
	must_read(&optlen, sizeof optlen);

	optval = (char *)malloc(optlen);
	if (!optval) {
		log_print("m_priv_setsockopt: malloc failed");
		close(sock);
		return;
	}

	must_read(optval, optlen);

	if (m_priv_check_sockopt(level, optname) != 0) {
		err = EACCES;
		v = -1;
	} else {
		v = setsockopt(sock, level, optname, optval, optlen);
		if (v < 0)
			err = errno;
	}

	close(sock);
	sock = -1;

	must_write(&err, sizeof err);
	must_write(&v, sizeof v);

	free(optval);
	return;
}

/* Privileged: called by monitor_loop.  */
static void
m_priv_bind(void)
{
	int		 sock, v, err = 0;
	struct sockaddr *name = 0;
	socklen_t        namelen;

	sock = mm_receive_fd(m_state.s);
	if (sock < 0) {
		log_print("m_priv_bind: read/write error");
		return;
	}

	must_read(&namelen, sizeof namelen);
	name = (struct sockaddr *)malloc(namelen);
	if (!name) {
		log_print("m_priv_bind: malloc failed");
		close(sock);
		return;
	}
	must_read((char *)name, namelen);

	if (m_priv_check_bind(name, namelen) != 0) {
		err = EACCES;
		v = -1;
	} else {
		v = bind(sock, name, namelen);
		if (v < 0) {
			log_error("m_priv_bind: bind(%d,%p,%d) returned %d",
			    sock, name, namelen, v);
			err = errno;
		}
	}

	close(sock);
	sock = -1;

	must_write(&err, sizeof err);
	must_write(&v, sizeof v);

	free(name);
	return;
}

/*
 * Help functions, used by both privileged and unprivileged code
 */

/*
 * 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(void *buf, size_t n)
{
        char *s = buf;
	size_t pos = 0;
        ssize_t res;

        while (n > pos) {
                res = read(m_state.s, s + pos, n - pos);
                switch (res) {
                case -1:
                        if (errno == EINTR || errno == EAGAIN)
                                continue;
                case 0:
			monitor_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(const void *buf, size_t n)
{
        const char *s = buf;
	size_t pos = 0;
	ssize_t res;

        while (n > pos) {
                res = write(m_state.s, s + pos, n - pos);
                switch (res) {
                case -1:
                        if (errno == EINTR || errno == EAGAIN)
                                continue;
                case 0:
			monitor_exit(0);
                default:
                        pos += res;
                }
        }
}

/* Check that path/mode is permitted.  */
static int
m_priv_local_sanitize_path(char *path, size_t pmax, int flags)
{
	char new_path[PATH_MAX], var_run[PATH_MAX];

	/*
	 * We only permit paths starting with
	 *  /etc/isakmpd/	(read only)
	 *  /var/run/		(rw)
         */

	if (realpath(path, new_path) == NULL ||
	    realpath("/var/run", var_run) == NULL) {
		/*
                 * We could not decide wether the path is ok or not.
                 * Indicate this be returning 2.
		 */
		if (errno == ENOENT)
			return 2;
		goto bad_path;
	}
	strlcat(var_run, "/", sizeof(var_run));

	if (strncmp(var_run, new_path, strlen(var_run)) == 0)
		return 0;

	if (strncmp(ISAKMPD_ROOT, new_path, strlen(ISAKMPD_ROOT)) == 0 &&
	    (flags & O_ACCMODE) == O_RDONLY)
		return 0;

bad_path:
	return 1;
}

/* Check setsockopt */
static int
m_priv_check_sockopt(int level, int name)
{
	switch (level) {
		/* These are allowed */
		case SOL_SOCKET:
		case IPPROTO_IP:
		case IPPROTO_IPV6:
		break;

	default:
		log_print("m_priv_check_sockopt: Illegal level %d", level);
		return 1;
	}

	switch (name) {
		/* These are allowed */
	case SO_REUSEPORT:
	case SO_REUSEADDR:
	case IP_AUTH_LEVEL:
	case IP_ESP_TRANS_LEVEL:
	case IP_ESP_NETWORK_LEVEL:
	case IP_IPCOMP_LEVEL:
	case IPV6_AUTH_LEVEL:
	case IPV6_ESP_TRANS_LEVEL:
	case IPV6_ESP_NETWORK_LEVEL:
	case IPV6_IPCOMP_LEVEL:
		break;

	default:
		log_print("m_priv_check_sockopt: Illegal option name %d",
		    name);
		return 1;
	}

	return 0;
}

/* Check bind */
static int
m_priv_check_bind(const struct sockaddr *sa, socklen_t salen)
{
	in_port_t       port;

	if (sa == NULL) {
		log_print("NULL address");
		return 1;
	}
	if (SA_LEN(sa) != salen) {
		log_print("Length mismatch: %lu %lu", (unsigned long)sa->sa_len,
		    (unsigned long)salen);
		return 1;
	}
	switch (sa->sa_family) {
	case AF_INET:
		if (salen != sizeof(struct sockaddr_in)) {
			log_print("Invalid inet address length");
			return 1;
		}
		port = ((const struct sockaddr_in *)sa)->sin_port;
		break;
	case AF_INET6:
		if (salen != sizeof(struct sockaddr_in6)) {
			log_print("Invalid inet6 address length");
			return 1;
		}
		port = ((const struct sockaddr_in6 *)sa)->sin6_port;
		break;
	default:
		log_print("Unknown address family");
		return 1;
	}

	port = ntohs(port);

	if (port != ISAKMP_PORT_DEFAULT && port < 1024) {
		log_print("Disallowed port %u", port);
		return 1;
	}
	return 0;
}

static void
m_priv_req_readdir()
{
	size_t len;
	char path[MAXPATHLEN];
	DIR *dp;
	struct dirent *file;
	struct stat sb;
	int off, size, fd, ret, serrno;

	must_read(&len, sizeof len);
	if (len == 0 || len >= sizeof path)
		log_fatal("m_priv_req_readdir: invalid pathname length");
	must_read(path, len);
	path[len] = '\0';
	if (strlen(path) != len)
		log_fatal("m_priv_req_readdir: invalid pathname");

	off = strlen(path);
	size = sizeof path - off;

	if ((dp = opendir(path)) == NULL) {
		serrno = errno;
		ret = -1;
		must_write(&ret, sizeof ret);
		must_write(&serrno, sizeof serrno);
		return;
	}

	/* report opendir() success */
	ret = 0;
	must_write(&ret, sizeof ret);

	while ((file = readdir(dp)) != NULL) {
		strlcpy(path + off, file->d_name, size);

		if (m_priv_local_sanitize_path(path, sizeof path, O_RDONLY)
		    != 0)
			continue;
		fd = open(path, O_RDONLY, 0);
		if (fd == -1) {
			log_error("m_priv_req_readdir: open "
			    "(\"%s\", O_RDONLY, 0) failed", path);
			continue;
		}
		if ((fstat(fd, &sb) == -1) ||
		    !(S_ISREG(sb.st_mode) || S_ISLNK(sb.st_mode))) {
			close(fd);
			continue;
		}

		len = strlen(path);
		must_write(&len, sizeof len);
		must_write(path, len);

		mm_send_fd(m_state.s, fd);
		close(fd);
	}
	closedir(dp);

	len = 0;
	must_write(&len, sizeof len);
}