src/libexec/ftpd/monitor.c

524 lines
11 KiB
C

/* $OpenBSD: monitor.c,v 1.31 2023/03/08 04:43:05 guenther Exp $ */
/*
* Copyright (c) 2004 Moritz Jodeit <moritz@openbsd.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/types.h>
#include <sys/socket.h>
#include <sys/wait.h>
#include <netinet/in.h>
#include <errno.h>
#include <fcntl.h>
#include <paths.h>
#include <pwd.h>
#include <signal.h>
#include <stdarg.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <syslog.h>
#include <unistd.h>
#include "monitor.h"
#include "extern.h"
enum monitor_command {
CMD_USER,
CMD_PASS,
CMD_SOCKET,
CMD_BIND
};
enum monitor_state {
PREAUTH,
POSTAUTH
};
extern char remotehost[];
extern char ttyline[20];
extern int debug;
extern void set_slave_signals(void);
int fd_monitor = -1;
int fd_slave = -1;
int nullfd;
pid_t slave_pid = -1;
enum monitor_state state = PREAUTH;
void send_data(int, void *, size_t);
void recv_data(int, void *, size_t);
void handle_cmds(void);
void set_monitor_signals(void);
void sig_pass_to_slave(int);
void sig_chld(int);
void fatalx(char *, ...);
void debugmsg(char *, ...);
/*
* Send data over a socket and exit if something fails.
*/
void
send_data(int sock, void *buf, size_t len)
{
ssize_t n;
size_t pos = 0;
char *ptr = buf;
while (len > pos) {
switch (n = write(sock, ptr + pos, len - pos)) {
case 0:
kill_slave("write failure");
_exit(0);
/* NOTREACHED */
case -1:
if (errno != EINTR && errno != EAGAIN)
fatalx("send_data: %m");
break;
default:
pos += n;
}
}
}
/*
* Receive data from socket and exit if something fails.
*/
void
recv_data(int sock, void *buf, size_t len)
{
ssize_t n;
size_t pos = 0;
char *ptr = buf;
while (len > pos) {
switch (n = read(sock, ptr + pos, len - pos)) {
case 0:
kill_slave(NULL);
_exit(0);
/* NOTREACHED */
case -1:
if (errno != EINTR && errno != EAGAIN)
fatalx("recv_data: %m");
break;
default:
pos += n;
}
}
}
void
set_monitor_signals(void)
{
struct sigaction act;
int i;
sigfillset(&act.sa_mask);
act.sa_flags = SA_RESTART;
act.sa_handler = SIG_DFL;
for (i = 1; i < _NSIG; i++)
sigaction(i, &act, NULL);
act.sa_handler = sig_chld;
sigaction(SIGCHLD, &act, NULL);
act.sa_handler = sig_pass_to_slave;
sigaction(SIGHUP, &act, NULL);
sigaction(SIGINT, &act, NULL);
sigaction(SIGQUIT, &act, NULL);
sigaction(SIGTERM, &act, NULL);
}
/*
* Creates the privileged monitor process. It returns twice.
* It returns 1 for the unprivileged slave process and 0 for the
* user-privileged slave process after successful authentication.
*/
int
monitor_init(void)
{
struct passwd *pw;
int pair[2];
if (socketpair(AF_LOCAL, SOCK_STREAM, PF_UNSPEC, pair) == -1)
fatalx("socketpair failed");
fd_monitor = pair[0];
fd_slave = pair[1];
set_monitor_signals();
slave_pid = fork();
if (slave_pid == -1)
fatalx("fork of unprivileged slave failed");
if (slave_pid == 0) {
/* Unprivileged slave */
set_slave_signals();
if ((pw = getpwnam(FTPD_PRIVSEP_USER)) == NULL)
fatalx("privilege separation user %s not found",
FTPD_PRIVSEP_USER);
if (chroot(pw->pw_dir) == -1)
fatalx("chroot %s: %m", pw->pw_dir);
if (chdir("/") == -1)
fatalx("chdir /: %m");
if (setgroups(1, &pw->pw_gid) == -1)
fatalx("setgroups: %m");
if (setresgid(pw->pw_gid, pw->pw_gid, pw->pw_gid) == -1)
fatalx("setresgid failed");
if (setresuid(pw->pw_uid, pw->pw_uid, pw->pw_uid) == -1)
fatalx("setresuid failed");
endpwent();
close(fd_slave);
return (1);
}
setproctitle("%s: [priv pre-auth]", remotehost);
handle_cmds();
/* User-privileged slave */
return (0);
}
/*
* Creates the user-privileged slave process. It is called
* from the privileged monitor process and returns twice. It returns 0
* for the user-privileged slave process and 1 for the monitor process.
*/
int
monitor_post_auth(void)
{
slave_pid = fork();
if (slave_pid == -1)
fatalx("fork of user-privileged slave failed");
snprintf(ttyline, sizeof(ttyline), "ftp%ld",
slave_pid == 0 ? (long)getpid() : (long)slave_pid);
if (slave_pid == 0) {
/* User privileged slave */
close(fd_slave);
set_slave_signals();
return (0);
}
/* We have to keep stdout open, because reply() needs it. */
if ((nullfd = open(_PATH_DEVNULL, O_RDWR)) == -1)
fatalx("cannot open %s: %m", _PATH_DEVNULL);
dup2(nullfd, STDIN_FILENO);
dup2(nullfd, STDERR_FILENO);
close(nullfd);
close(fd_monitor);
return (1);
}
/*
* Handles commands received from the slave process. It will not return
* except in one situation: After successful authentication it will
* return as the user-privileged slave process.
*/
void
handle_cmds(void)
{
enum monitor_command cmd;
enum auth_ret auth;
int err, s, slavequit, serrno, domain;
pid_t preauth_slave_pid;
size_t len;
union sockunion sa;
socklen_t salen;
char *name, *pw;
for (;;) {
recv_data(fd_slave, &cmd, sizeof(cmd));
switch (cmd) {
case CMD_USER:
debugmsg("CMD_USER received");
recv_data(fd_slave, &len, sizeof(len));
if (len == SIZE_MAX)
fatalx("monitor received invalid user length");
if ((name = malloc(len + 1)) == NULL)
fatalx("malloc: %m");
if (len > 0)
recv_data(fd_slave, name, len);
name[len] = '\0';
user(name);
free(name);
break;
case CMD_PASS:
debugmsg("CMD_PASS received");
recv_data(fd_slave, &len, sizeof(len));
if (len == SIZE_MAX)
fatalx("monitor received invalid pass length");
if ((pw = malloc(len + 1)) == NULL)
fatalx("malloc: %m");
if (len > 0)
recv_data(fd_slave, pw, len);
pw[len] = '\0';
preauth_slave_pid = slave_pid;
auth = pass(pw);
freezero(pw, len);
switch (auth) {
case AUTH_FAILED:
/* Authentication failure */
debugmsg("authentication failed");
slavequit = 0;
send_data(fd_slave, &slavequit,
sizeof(slavequit));
break;
case AUTH_SLAVE:
if (pledge("stdio rpath wpath cpath inet recvfd"
" sendfd proc tty getpw", NULL) == -1)
fatalx("pledge");
/* User-privileged slave */
debugmsg("user-privileged slave started");
return;
/* NOTREACHED */
case AUTH_MONITOR:
if (pledge("stdio inet sendfd recvfd proc",
NULL) == -1)
fatalx("pledge");
/* Post-auth monitor */
debugmsg("monitor went into post-auth phase");
state = POSTAUTH;
setproctitle("%s: [priv post-auth]",
remotehost);
slavequit = 1;
send_data(fd_slave, &slavequit,
sizeof(slavequit));
while (waitpid(preauth_slave_pid, NULL, 0) == -1 &&
errno == EINTR)
;
break;
default:
fatalx("bad return value from pass()");
/* NOTREACHED */
}
break;
case CMD_SOCKET:
debugmsg("CMD_SOCKET received");
if (state != POSTAUTH)
fatalx("CMD_SOCKET received in invalid state");
recv_data(fd_slave, &domain, sizeof(domain));
if (domain != AF_INET && domain != AF_INET6)
fatalx("monitor received invalid addr family");
s = socket(domain, SOCK_STREAM, 0);
serrno = errno;
send_fd(fd_slave, s);
if (s == -1)
send_data(fd_slave, &serrno, sizeof(serrno));
else
close(s);
break;
case CMD_BIND:
debugmsg("CMD_BIND received");
if (state != POSTAUTH)
fatalx("CMD_BIND received in invalid state");
s = recv_fd(fd_slave);
recv_data(fd_slave, &salen, sizeof(salen));
if (salen == 0 || salen > sizeof(sa))
fatalx("monitor received invalid sockaddr len");
bzero(&sa, sizeof(sa));
recv_data(fd_slave, &sa, salen);
if (sa.su_si.si_len != salen)
fatalx("monitor received invalid sockaddr len");
if (sa.su_si.si_family != AF_INET &&
sa.su_si.si_family != AF_INET6)
fatalx("monitor received invalid addr family");
err = bind(s, (struct sockaddr *)&sa, salen);
serrno = errno;
if (s >= 0)
close(s);
send_data(fd_slave, &err, sizeof(err));
if (err == -1)
send_data(fd_slave, &serrno, sizeof(serrno));
break;
default:
fatalx("monitor received unknown command %d", cmd);
/* NOTREACHED */
}
}
}
void
sig_pass_to_slave(int signo)
{
int olderrno = errno;
if (slave_pid > 0)
kill(slave_pid, signo);
errno = olderrno;
}
void
sig_chld(int signo)
{
pid_t pid;
int stat, olderrno = errno;
do {
pid = waitpid(slave_pid, &stat, WNOHANG);
if (pid > 0)
_exit(0);
} while (pid == -1 && errno == EINTR);
errno = olderrno;
}
void
kill_slave(char *reason)
{
if (slave_pid > 0) {
if (reason)
syslog(LOG_NOTICE, "kill slave %d: %s",
slave_pid, reason);
kill(slave_pid, SIGQUIT);
}
}
void
fatalx(char *fmt, ...)
{
va_list ap;
va_start(ap, fmt);
vsyslog(LOG_ERR, fmt, ap);
va_end(ap);
kill_slave("fatal error");
_exit(0);
}
void
debugmsg(char *fmt, ...)
{
va_list ap;
if (debug) {
va_start(ap, fmt);
vsyslog(LOG_DEBUG, fmt, ap);
va_end(ap);
}
}
void
monitor_user(char *name)
{
enum monitor_command cmd;
size_t len;
cmd = CMD_USER;
send_data(fd_monitor, &cmd, sizeof(cmd));
len = strlen(name);
send_data(fd_monitor, &len, sizeof(len));
if (len > 0)
send_data(fd_monitor, name, len);
}
int
monitor_pass(char *pass)
{
enum monitor_command cmd;
int quitnow;
size_t len;
cmd = CMD_PASS;
send_data(fd_monitor, &cmd, sizeof(cmd));
len = strlen(pass);
send_data(fd_monitor, &len, sizeof(len));
if (len > 0)
send_data(fd_monitor, pass, len);
recv_data(fd_monitor, &quitnow, sizeof(quitnow));
return (quitnow);
}
int
monitor_socket(int domain)
{
enum monitor_command cmd;
int s, serrno;
cmd = CMD_SOCKET;
send_data(fd_monitor, &cmd, sizeof(cmd));
send_data(fd_monitor, &domain, sizeof(domain));
s = recv_fd(fd_monitor);
if (s == -1) {
recv_data(fd_monitor, &serrno, sizeof(serrno));
errno = serrno;
}
return (s);
}
int
monitor_bind(int s, struct sockaddr *name, socklen_t namelen)
{
enum monitor_command cmd;
int ret, serrno;
cmd = CMD_BIND;
send_data(fd_monitor, &cmd, sizeof(cmd));
send_fd(fd_monitor, s);
send_data(fd_monitor, &namelen, sizeof(namelen));
send_data(fd_monitor, name, namelen);
recv_data(fd_monitor, &ret, sizeof(ret));
if (ret == -1) {
recv_data(fd_monitor, &serrno, sizeof(serrno));
errno = serrno;
}
return (ret);
}