root/usr.sbin/lpd/control.c
/*      $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);
        }
}