Edit

IABSD.fr/src/libexec/ftpd/monitor.c

Branch :

  • Show log

    Commit

  • Author : deraadt
    Date : 2025-05-08 15:22:49
    Hash : 5a61f308
    Message : There is no call to setpassent(1) or to getpwent(), so endpwent() is not required. Strangely, this pattern of calling endpwent() after getpwuid() or getpwnam() has become quite common, and I have a bunch of mop-up diffs coming. Discussed with millert.

  • libexec/ftpd/monitor.c
  • /*	$OpenBSD: monitor.c,v 1.32 2025/05/08 15:22:49 deraadt 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");
    
    		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);
    }