OpenBSD-4.6/libexec/ftpd/monitor.c

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

/*	$OpenBSD: monitor.c,v 1.20 2009/06/04 01:12:39 sthen 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 <limits.h>
#include <paths.h>
#include <pwd.h>
#include <signal.h>
#include <stdarg.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
};

#ifdef HASSETPROCTITLE
extern char	remotehost[];
#endif
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 (setegid(pw->pw_gid) == -1)
			fatalx("setegid failed");
		if (setgid(pw->pw_gid) == -1)
			fatalx("setgid failed");
		if (seteuid(pw->pw_uid) == -1)
			fatalx("seteuid failed");
		if (setuid(pw->pw_uid) == -1)
			fatalx("setuid failed");

		endpwent();
		close(fd_slave);
		return (1);
	}

#ifdef HASSETPROCTITLE
	setproctitle("%s: [priv pre-auth]", remotehost);
#endif

	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()
{
	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, 0)) == -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_T_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_T_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);
			bzero(pw, len);
			free(pw);

			switch (auth) {
			case AUTH_FAILED:
				/* Authentication failure */
				debugmsg("authentication failed");
				slavequit = 0;
				send_data(fd_slave, &slavequit,
				    sizeof(slavequit));
				break;
			case AUTH_SLAVE:
				/* User-privileged slave */
				debugmsg("user-privileged slave started");
				return;
				/* NOTREACHED */
			case AUTH_MONITOR:
				/* Post-auth monitor */
				debugmsg("monitor went into post-auth phase");
				state = POSTAUTH;
#ifdef HASSETPROCTITLE
				setproctitle("%s: [priv post-auth]",
				    remotehost);
#endif
				slavequit = 1;

				send_data(fd_slave, &slavequit,
				    sizeof(slavequit));

				while (waitpid(preauth_slave_pid, NULL, 0) < 0 &&
				    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;
}

/* ARGSUSED */
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);
}