[BACK]Return to control.c CVS log [TXT][DIR] Up to [local] / src / usr.sbin / lpd

File: [local] / src / usr.sbin / lpd / control.c (download)

Revision 1.3, Fri Mar 22 19:14:28 2024 UTC (2 months, 2 weeks ago) by bluhm
Branch: MAIN
CVS Tags: HEAD
Changes since 1.2: +7 -4 lines

Fix chroot(2) call in control process.

Use /var/empty as chroot directory.  Call chroot(2) before setresuid(2).
Do the error check correctly.  Call chdir(2) after chroot(2).

from spiros thanasoulas; with florian@ tb@; OK millert@

/*	$OpenBSD: control.c,v 1.3 2024/03/22 19:14:28 bluhm Exp $	*/

/*
 * Copyright (c) 2017 Eric Faurot <eric@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/queue.h>
#include <sys/stat.h>
#include <sys/socket.h>
#include <sys/un.h>

#include <errno.h>
#include <event.h>
#include <imsg.h>
#include <paths.h>
#include <pwd.h>
#include <signal.h>
#include <stdlib.h>
#include <string.h>
#include <syslog.h>
#include <unistd.h>

#include "lpd.h"

#include "log.h"
#include "proc.h"

#define	CONTROL_BACKLOG	5

static void control_init(const char *);
static void control_listen(void);
static void control_pause(void);
static void control_resume(void);
static void control_accept(int, short, void *);
static void control_close(struct imsgproc *);
static void control_dispatch_priv(struct imsgproc *, struct imsg *, void *);
static void control_dispatch_client(struct imsgproc *, struct imsg *, void *);

static struct {
	struct event	evt;
	int		fd;
	int		pause;
} ctl;

void
control(int debug, int verbose)
{
	struct passwd *pw;

	/* Early initialisation. */
	log_init(debug, LOG_DAEMON);
	log_setverbose(verbose);
	log_procinit("control");
	setproctitle("control");

	control_init(LPD_SOCKET);

	/* Drop privileges. */
	if ((pw = getpwnam(LPD_USER)) == NULL)
		fatalx("unknown user " LPD_USER);

	if (chroot(_PATH_VAREMPTY) == -1)
		fatal("%s: chroot", __func__);
	if (chdir("/") == -1)
		fatal("%s: chdir", __func__);

	if (setgroups(1, &pw->pw_gid) ||
	    setresgid(pw->pw_gid, pw->pw_gid, pw->pw_gid) ||
	    setresuid(pw->pw_uid, pw->pw_uid, pw->pw_uid))
		fatal("cannot drop privileges");

	if (pledge("stdio unix recvfd sendfd", NULL) == -1)
		fatal("%s: pledge", __func__);

	event_init();

	signal(SIGPIPE, SIG_IGN);

	/* Setup imsg socket with parent. */
	p_priv = proc_attach(PROC_PRIV, 3);
	if (p_priv == NULL)
		fatal("%s: proc_attach", __func__);
	proc_setcallback(p_priv, control_dispatch_priv, NULL);
	proc_enable(p_priv);

	event_dispatch();

	exit(0);
}

static void
control_init(const char *path)
{
	struct sockaddr_un sun;
	mode_t old_umask;
	int fd;

	fd = socket(AF_UNIX, SOCK_STREAM | SOCK_CLOEXEC | SOCK_NONBLOCK, 0);
	if (fd == -1)
		fatal("%s: socket", __func__);

	memset(&sun, 0, sizeof(sun));
	sun.sun_family = AF_UNIX;
	strlcpy(sun.sun_path, LPD_SOCKET, sizeof(sun.sun_path));

	if ((unlink(path) == -1) && (errno != ENOENT))
		fatal("%s: unlink: %s", __func__, path);

	old_umask = umask(S_IXUSR|S_IXGRP|S_IWOTH|S_IROTH|S_IXOTH);
	if (bind(fd, (struct sockaddr *)&sun, sizeof(sun)) == -1)
		fatal("%s: bind: %s", __func__, path);
	umask(old_umask);

	if (chmod(path, S_IRUSR|S_IWUSR|S_IRGRP|S_IWGRP) == -1)
		fatal("%s: chmod: %s", __func__, path);

	ctl.fd = fd;
}

static void
control_listen(void)
{
	if (listen(ctl.fd, CONTROL_BACKLOG) == -1)
		fatal("%s: listen", __func__);

	ctl.pause = 0;
	control_resume();
}

static void
control_pause(void)
{
	struct timeval tv;

	event_del(&ctl.evt);

	tv.tv_sec = 1;
	tv.tv_usec = 0;

	evtimer_set(&ctl.evt, control_accept, NULL);
	evtimer_add(&ctl.evt, &tv);
	ctl.pause = 1;
}

static void
control_resume(void)
{
	if (ctl.pause) {
		evtimer_del(&ctl.evt);
		ctl.pause = 0;
	}
	event_set(&ctl.evt, ctl.fd, EV_READ | EV_PERSIST, control_accept, NULL);
	event_add(&ctl.evt, NULL);
}

static void
control_accept(int fd, short event, void *arg)
{
	struct imsgproc *proc;
	int sock;

	if (ctl.pause) {
		ctl.pause = 0;
		control_resume();
		return;
	}

	sock = accept4(ctl.fd, NULL, NULL, SOCK_CLOEXEC | SOCK_NONBLOCK);
	if (sock == -1) {
		if (errno == ENFILE || errno == EMFILE)
			control_pause();
		else if (errno != EWOULDBLOCK && errno != EINTR &&
		    errno != ECONNABORTED)
			log_warn("%s: accept4", __func__);
		return;
	}

	proc = proc_attach(PROC_CLIENT, sock);
	if (proc == NULL) {
		log_warn("%s: proc_attach", __func__);
		close(sock);
		return;
	}
	proc_setcallback(proc, control_dispatch_client, NULL);
	proc_enable(proc);
}

static void
control_close(struct imsgproc *proc)
{
	proc_free(proc);

	if (ctl.pause)
		control_resume();
}

static void
control_dispatch_priv(struct imsgproc *proc, struct imsg *imsg, void *arg)
{
	if (imsg == NULL) {
		log_debug("%s: imsg connection lost", __func__);
		event_loopexit(NULL);
		return;
	}

	if (log_getverbose() > LOGLEVEL_IMSG)
		log_imsg(proc, imsg);

	switch (imsg->hdr.type) {
	case IMSG_CONF_START:
		m_end(proc);
		break;

	case IMSG_CONF_END:
		m_end(proc);
		control_listen();
		break;

	default:
		fatalx("%s: unexpected imsg %s", __func__,
		    log_fmt_imsgtype(imsg->hdr.type));
	}
}

static void
control_dispatch_client(struct imsgproc *proc, struct imsg *imsg, void *arg)
{
	if (imsg == NULL) {
		control_close(proc);
		return;
	}

	if (log_getverbose() > LOGLEVEL_IMSG)
		log_imsg(proc, imsg);

	switch (imsg->hdr.type) {
	default:
		log_debug("%s: error handling imsg %d", __func__,
		    imsg->hdr.type);
	}
}