885 lines
18 KiB
C
885 lines
18 KiB
C
/* $OpenBSD: monitor.c,v 1.83 2023/02/08 08:03:11 tb 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/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 <limits.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[PATH_MAX];
|
|
} 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(const 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 = 0, gotstatus = 0;
|
|
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);
|
|
if (pid != -1)
|
|
gotstatus = 1;
|
|
|
|
/* Remove FIFO and pid files. */
|
|
unlink(ui_fifo);
|
|
unlink(pid_file);
|
|
}
|
|
|
|
close(m_state.s);
|
|
if (code == 0 && gotstatus)
|
|
exit(WIFEXITED(status)? WEXITSTATUS(status) : 1);
|
|
else
|
|
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[PATH_MAX];
|
|
|
|
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[PATH_MAX];
|
|
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 (errno != ENOENT)
|
|
log_print("m_priv_getfd: illegal path \"%s\"", path);
|
|
err = errno;
|
|
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 = 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 = 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 == -1) {
|
|
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(const char *path, size_t pmax, int flags)
|
|
{
|
|
char new_path[PATH_MAX], var_run[PATH_MAX], *enddir;
|
|
|
|
/*
|
|
* We only permit paths starting with
|
|
* /etc/isakmpd/ (read only)
|
|
* /var/run/ (rw)
|
|
*/
|
|
|
|
if (realpath(path, new_path) == NULL) {
|
|
if (errno != ENOENT)
|
|
return 1;
|
|
/*
|
|
* It is ok if the directory exists,
|
|
* but the file should be created.
|
|
*/
|
|
if (strlcpy(new_path, path, sizeof(new_path)) >=
|
|
sizeof(new_path))
|
|
return 1;
|
|
enddir = strrchr(new_path, '/');
|
|
if (enddir == NULL || enddir[1] == '\0')
|
|
return 1;
|
|
enddir[1] = '\0';
|
|
if (realpath(new_path, new_path) == NULL) {
|
|
errno = ENOENT;
|
|
return 1;
|
|
}
|
|
enddir = strrchr(path, '/');
|
|
strlcat(new_path, enddir, sizeof(new_path));
|
|
}
|
|
|
|
if (realpath("/var/run/", var_run) == NULL)
|
|
return 1;
|
|
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;
|
|
|
|
errno = EACCES;
|
|
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(void)
|
|
{
|
|
size_t len;
|
|
char path[PATH_MAX];
|
|
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);
|
|
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);
|
|
}
|