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

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

Revision 1.3, Wed Dec 28 21:30:17 2022 UTC (17 months, 1 week ago) by jmc
Branch: MAIN
CVS Tags: OPENBSD_7_5_BASE, OPENBSD_7_5, OPENBSD_7_4_BASE, OPENBSD_7_4, OPENBSD_7_3_BASE, OPENBSD_7_3, HEAD
Changes since 1.2: +2 -2 lines

spelling fixes; from paul tagliamonte
any parts of his diff not taken are noted on tech

/*	$OpenBSD: frontend.c,v 1.3 2022/12/28 21:30:17 jmc 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/tree.h>

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

#include "lpd.h"

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

static void frontend_shutdown(void);
static void frontend_listen(struct listener *);
static void frontend_pause(struct listener *);
static void frontend_resume(struct listener *);
static void frontend_accept(int, short, void *);
static void frontend_dispatch_priv(struct imsgproc *, struct imsg *, void *);
static void frontend_dispatch_engine(struct imsgproc *, struct imsg *, void *);

struct conn {
	SPLAY_ENTRY(conn)	 entry;
	struct listener		*listener;
	uint32_t		 id;
};

static int conn_cmp(struct conn *, struct conn *);

SPLAY_HEAD(conntree, conn);
SPLAY_PROTOTYPE(conntree, conn, entry, conn_cmp);

static struct conntree conns;
static struct lpd_conf *tmpconf;

static int
conn_cmp(struct conn *a, struct conn *b)
{
	if (a->id < b->id)
		return (-1);
	if (a->id > b->id)
		return (1);
	return (0);
}

SPLAY_GENERATE(conntree, conn, entry, conn_cmp);

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

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

	SPLAY_INIT(&conns);
	lpr_init();

	/* Drop privileges. */
	if ((pw = getpwnam(LPD_USER)) == NULL)
		fatal("%s: getpwnam: %s", __func__, 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("%s: cannot drop privileges", __func__);

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

	event_init();

	signal(SIGPIPE, SIG_IGN);

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

	event_dispatch();

	frontend_shutdown();
}

void
frontend_conn_closed(uint32_t connid)
{
	struct listener *l;
	struct conn key, *conn;

	key.id = connid;
	conn = SPLAY_FIND(conntree, &conns, &key);
	if (conn == NULL)
		fatalx("%s: %08x unknown connid", __func__, connid);

	l = conn->listener;

	if (log_getverbose() > LOGLEVEL_CONN)
		log_debug("%08x close %s", conn->id,
		    log_fmt_proto(l->proto));

	SPLAY_REMOVE(conntree, &conns, conn);
	free(conn);

	if (l->pause)
		frontend_resume(l);
}

static void
frontend_shutdown()
{
	struct listener *l;

	TAILQ_FOREACH(l, &env->listeners, entry)
		close(l->sock);

	log_debug("exiting");

	exit(0);
}

static void
frontend_listen(struct listener *l)
{
	if (log_getverbose() > LOGLEVEL_CONN)
		log_debug("listen %s %s", log_fmt_proto(l->proto),
		    log_fmt_sockaddr((struct sockaddr*)&l->ss));

	if (listen(l->sock, 5) == -1)
		fatal("%s: listen", __func__);

	frontend_resume(l);
}

static void
frontend_pause(struct listener *l)
{
	struct timeval tv;

	event_del(&l->ev);

	tv.tv_sec = 2;
	tv.tv_usec = 0;

	evtimer_set(&l->ev, frontend_accept, l);
	evtimer_add(&l->ev, &tv);
	l->pause = 1;
}

static void
frontend_resume(struct listener *l)
{
	if (l->pause) {
		evtimer_del(&l->ev);
		l->pause = 0;
	}
	event_set(&l->ev, l->sock, EV_READ | EV_PERSIST, frontend_accept, l);
	event_add(&l->ev, NULL);
}

static void
frontend_accept(int sock, short ev, void *arg)
{
	struct listener *l = arg;
	struct sockaddr_storage ss;
	struct sockaddr *sa;
	struct conn *conn;
	socklen_t len;

	if (l->pause) {
		l->pause = 0;
		frontend_resume(l);
		return;
	}

	conn = calloc(1, sizeof(*conn));
	if (conn == NULL)
		log_warn("%s: calloc", __func__);

	sa = (struct sockaddr *)&ss;
	len = sizeof(ss);
	sock = accept4(sock, sa, &len, SOCK_NONBLOCK);
	if (sock == -1) {
		if (errno == ENFILE || errno == EMFILE)
			frontend_pause(l);
		else if (errno != EWOULDBLOCK && errno != EINTR &&
		    errno != ECONNABORTED)
			log_warn("%s: accept4", __func__);
		free(conn);
		return;
	}

	if (conn == NULL) {
		close(sock);
		return;
	}

	while (conn->id == 0 || SPLAY_FIND(conntree, &conns, conn))
		conn->id = arc4random();
	SPLAY_INSERT(conntree, &conns, conn);
	conn->listener = l;

	if (log_getverbose() > LOGLEVEL_CONN)
		log_debug("%08x accept %s %s", conn->id,
		    log_fmt_proto(conn->listener->proto),
		    log_fmt_sockaddr((struct sockaddr*)&ss));

	switch (l->proto) {
	case PROTO_LPR:
		lpr_conn(conn->id, l, sock, sa);
		break;
	default:
		fatalx("%s: unexpected protocol %d", __func__, l->proto);
	}
}

static void
frontend_dispatch_priv(struct imsgproc *proc, struct imsg *imsg, void *arg)
{
	struct listener *l;

	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_SOCK_ENGINE:
		if (imsg->fd == -1)
			fatalx("%s: engine socket not received", __func__);
		m_end(proc);
		p_engine = proc_attach(PROC_ENGINE, imsg->fd);
		proc_setcallback(p_engine, frontend_dispatch_engine, NULL);
		proc_enable(p_engine);
		break;

	case IMSG_CONF_START:
		m_end(proc);
		if ((tmpconf = calloc(1, sizeof(*tmpconf))) == NULL)
			fatal("%s: calloc", __func__);
		TAILQ_INIT(&tmpconf->listeners);
		break;

	case IMSG_CONF_LISTENER:
		if (imsg->fd == -1)
			fatalx("%s: listener socket not received", __func__);
		if ((l = calloc(1, sizeof(*l))) == NULL)
			fatal("%s: calloc", __func__);
		m_get_int(proc, &l->proto);
		m_get_sockaddr(proc, (struct sockaddr *)&l->ss);
		m_end(proc);
		l->sock = imsg->fd;
		TAILQ_INSERT_TAIL(&tmpconf->listeners, l, entry);
		break;

	case IMSG_CONF_END:
		m_end(proc);
		TAILQ_FOREACH(l, &tmpconf->listeners, entry)
			frontend_listen(l);
		env = tmpconf;
		break;

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

static void
frontend_dispatch_engine(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_GETADDRINFO:
	case IMSG_GETADDRINFO_END:
	case IMSG_GETNAMEINFO:
		resolver_dispatch_result(proc, imsg);
		break;

	case IMSG_LPR_ALLOWEDHOST:
	case IMSG_LPR_DISPLAYQ:
	case IMSG_LPR_RECVJOB:
	case IMSG_LPR_RECVJOB_CF:
	case IMSG_LPR_RECVJOB_DF:
	case IMSG_LPR_RMJOB:
		lpr_dispatch_engine(proc, imsg);
		break;

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