summaryrefslogtreecommitdiff
path: root/usr.sbin/lpd/lpd.c
diff options
context:
space:
mode:
Diffstat (limited to 'usr.sbin/lpd/lpd.c')
-rw-r--r--usr.sbin/lpd/lpd.c461
1 files changed, 461 insertions, 0 deletions
diff --git a/usr.sbin/lpd/lpd.c b/usr.sbin/lpd/lpd.c
new file mode 100644
index 00000000000..fbbe5a7069e
--- /dev/null
+++ b/usr.sbin/lpd/lpd.c
@@ -0,0 +1,461 @@
+/* $OpenBSD: lpd.c,v 1.1.1.1 2018/04/27 16:14:37 eric 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/stat.h>
+#include <sys/un.h>
+#include <sys/wait.h>
+
+#include <errno.h>
+#include <getopt.h>
+#include <pwd.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <signal.h>
+#include <string.h>
+#include <syslog.h>
+#include <unistd.h>
+
+#include "lpd.h"
+
+#include "log.h"
+#include "proc.h"
+
+struct lpd_conf *env;
+struct imsgproc *p_control;
+struct imsgproc *p_engine;
+struct imsgproc *p_frontend;
+struct imsgproc *p_priv;
+
+static void priv_dispatch_control(struct imsgproc *, struct imsg *, void *);
+static void priv_dispatch_engine(struct imsgproc *, struct imsg *, void *);
+static void priv_dispatch_frontend(struct imsgproc *, struct imsg *, void *);
+static void priv_dispatch_printer(struct imsgproc *, struct imsg *, void *);
+static void priv_open_listener(struct listener *);
+static void priv_send_config(void);
+static void priv_sighandler(int, short, void *);
+static void priv_shutdown(void);
+static void priv_run_printer(const char *);
+
+static char **saved_argv;
+static int saved_argc;
+
+static void
+usage(void)
+{
+ extern char *__progname;
+
+ fprintf(stderr, "usage: %s [-dnv] [-D macro=value] [-f file]\n",
+ __progname);
+ exit(1);
+}
+
+int
+main(int argc, char **argv)
+{
+ struct listener *l;
+ struct event evt_sigchld, evt_sigint, evt_sigterm, evt_sighup;
+ const char *conffile = LPD_CONFIG, *reexec = NULL;
+ int sp[2], ch, debug = 0, nflag = 0, verbose = 1;
+
+ saved_argv = argv;
+ saved_argc = argc;
+
+ log_init(1, LOG_LPR);
+ log_setverbose(0);
+
+ while ((ch = getopt(argc, argv, "D:df:nvX:")) != -1) {
+ switch (ch) {
+ case 'D':
+ if (cmdline_symset(optarg) < 0)
+ log_warnx("could not parse macro definition %s",
+ optarg);
+ break;
+ case 'd':
+ debug = 1;
+ break;
+ case 'f':
+ conffile = optarg;
+ break;
+ case 'n':
+ nflag = 1;
+ break;
+ case 'v':
+ verbose++;
+ break;
+ case 'X':
+ reexec = optarg;
+ break;
+ default:
+ usage();
+ }
+ }
+
+ argv += optind;
+ argc -= optind;
+
+ if (argc || *argv)
+ usage();
+
+ if (reexec) {
+ if (!strcmp(reexec, "control"))
+ control(debug, verbose);
+ if (!strcmp(reexec, "engine"))
+ engine(debug, verbose);
+ if (!strcmp(reexec, "frontend"))
+ frontend(debug, verbose);
+ if (!strncmp(reexec, "printer:", 8))
+ printer(debug, verbose, strchr(reexec, ':') + 1);
+ fatalx("unknown process %s", reexec);
+ }
+
+ /* Parse config file. */
+ env = parse_config(conffile, verbose);
+ if (env == NULL)
+ exit(1);
+
+ if (nflag) {
+ fprintf(stderr, "configuration OK\n");
+ exit(0);
+ }
+
+ /* Check for root privileges. */
+ if (geteuid())
+ fatalx("need root privileges");
+
+ /* Check for assigned daemon user. */
+ if (getpwnam(LPD_USER) == NULL)
+ fatalx("unknown user %s", LPD_USER);
+
+ log_init(debug, LOG_LPR);
+ log_setverbose(verbose);
+ log_procinit("priv");
+ setproctitle("priv");
+
+ if (!debug)
+ if (daemon(1, 0) == -1)
+ fatal("daemon");
+
+ log_info("startup");
+
+ TAILQ_FOREACH(l, &env->listeners, entry)
+ priv_open_listener(l);
+
+ event_init();
+
+ signal_set(&evt_sigint, SIGINT, priv_sighandler, NULL);
+ signal_add(&evt_sigint, NULL);
+ signal_set(&evt_sigterm, SIGTERM, priv_sighandler, NULL);
+ signal_add(&evt_sigterm, NULL);
+ signal_set(&evt_sigchld, SIGCHLD, priv_sighandler, NULL);
+ signal_add(&evt_sigchld, NULL);
+ signal_set(&evt_sighup, SIGHUP, priv_sighandler, NULL);
+ signal_add(&evt_sighup, NULL);
+ signal(SIGPIPE, SIG_IGN);
+
+ /* Fork and exec unpriviledged processes. */
+ argv = calloc(saved_argc + 3, sizeof(*argv));
+ if (argv == NULL)
+ fatal("calloc");
+ for (argc = 0; argc < saved_argc; argc++)
+ argv[argc] = saved_argv[argc];
+ argv[argc++] = "-X";
+ argv[argc++] = "";
+ argv[argc++] = NULL;
+
+ argv[argc - 2] = "control";
+ p_control = proc_exec(PROC_CONTROL, argv);
+ if (p_control == NULL)
+ fatalx("cannot exec control process");
+ proc_setcallback(p_control, priv_dispatch_control, NULL);
+ proc_enable(p_control);
+
+ argv[argc - 2] = "engine";
+ p_engine = proc_exec(PROC_ENGINE, argv);
+ if (p_engine == NULL)
+ fatalx("cannot exec engine process");
+ proc_setcallback(p_engine, priv_dispatch_engine, NULL);
+ proc_enable(p_engine);
+
+ argv[argc - 2] = "frontend";
+ p_frontend = proc_exec(PROC_FRONTEND, argv);
+ if (p_frontend == NULL)
+ fatalx("cannot exec frontend process");
+ proc_setcallback(p_frontend, priv_dispatch_frontend, NULL);
+ proc_enable(p_frontend);
+
+ /* Connect processes. */
+ if (socketpair(AF_UNIX, SOCK_STREAM|SOCK_NONBLOCK, PF_UNSPEC, sp) == -1)
+ fatal("socketpair");
+ m_compose(p_engine, IMSG_SOCK_FRONTEND, 0, 0, sp[1], NULL, 0);
+ m_compose(p_frontend, IMSG_SOCK_ENGINE, 0, 0, sp[0], NULL, 0);
+
+ priv_send_config();
+
+ if (pledge("stdio sendfd proc exec", NULL) == -1)
+ fatal("pledge");
+
+ event_dispatch();
+
+ priv_shutdown();
+
+ return (0);
+}
+
+static void
+priv_sighandler(int sig, short ev, void *arg)
+{
+ pid_t pid;
+ int status;
+
+ switch (sig) {
+ case SIGTERM:
+ case SIGINT:
+ event_loopbreak();
+ break;
+ case SIGCHLD:
+ do {
+ pid = waitpid(-1, &status, WNOHANG);
+ if (pid <= 0)
+ continue;
+ if (WIFSIGNALED(status))
+ log_warnx("process %d terminated by signal %d",
+ (int)pid, WTERMSIG(status));
+ else if (WIFEXITED(status) && WEXITSTATUS(status))
+ log_warnx("process %d exited with status %d",
+ (int)pid, WEXITSTATUS(status));
+ else if (WIFEXITED(status))
+ log_debug("process %d exited normally",
+ (int)pid);
+ else
+ /* WIFSTOPPED or WIFCONTINUED */
+ continue;
+ } while (pid > 0 || (pid == -1 && errno == EINTR));
+ break;
+ default:
+ fatalx("signal %d", sig);
+ }
+}
+
+static void
+priv_shutdown(void)
+{
+ pid_t pid;
+
+ proc_free(p_control);
+ proc_free(p_engine);
+ proc_free(p_frontend);
+
+ do {
+ pid = waitpid(WAIT_MYPGRP, NULL, 0);
+ } while (pid != -1 || (pid == -1 && errno == EINTR));
+
+ log_info("exiting");
+
+ exit(0);
+}
+
+static void
+priv_open_listener(struct listener *l)
+{
+ struct sockaddr_un *su;
+ struct sockaddr *sa;
+ const char *path;
+ mode_t old_umask;
+ int opt, sock, r;
+
+ sa = (struct sockaddr *)&l->ss;
+
+ sock = socket(sa->sa_family, SOCK_STREAM | SOCK_NONBLOCK, 0);
+ if (sock == -1) {
+ if (errno == EAFNOSUPPORT) {
+ log_warn("%s: socket", __func__);
+ return;
+ }
+ fatal("%s: socket", __func__);
+ }
+
+ switch (sa->sa_family) {
+ case AF_LOCAL:
+ su = (struct sockaddr_un *)sa;
+ path = su->sun_path;
+ if (connect(sock, sa, sa->sa_len) == 0)
+ fatalx("%s already in use", path);
+
+ if (unlink(path) == -1)
+ if (errno != ENOENT)
+ fatal("unlink: %s", path);
+
+ old_umask = umask(S_IXUSR|S_IXGRP|S_IWOTH|S_IROTH|S_IXOTH);
+ r = bind(sock, sa, sizeof(*su));
+ (void)umask(old_umask);
+
+ if (r == -1)
+ fatal("bind: %s", path);
+ break;
+
+ case AF_INET:
+ case AF_INET6:
+ opt = 1;
+ if (setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, &opt,
+ sizeof(opt)) < 0)
+ fatal("setsockopt: %s", log_fmt_sockaddr(sa));
+
+ if (bind(sock, sa, sa->sa_len) == -1)
+ fatal("bind: %s", log_fmt_sockaddr(sa));
+ break;
+
+ default:
+ fatalx("bad address family %d", sa->sa_family);
+ }
+
+ l->sock = sock;
+}
+
+static void
+priv_send_config(void)
+{
+ struct listener *l;
+
+ m_compose(p_control, IMSG_CONF_START, 0, 0, -1, NULL, 0);
+ m_compose(p_control, IMSG_CONF_END, 0, 0, -1, NULL, 0);
+
+ m_compose(p_engine, IMSG_CONF_START, 0, 0, -1, NULL, 0);
+ m_compose(p_engine, IMSG_CONF_END, 0, 0, -1, NULL, 0);
+
+ m_compose(p_frontend, IMSG_CONF_START, 0, 0, -1, NULL, 0);
+ TAILQ_FOREACH(l, &env->listeners, entry) {
+ m_create(p_frontend, IMSG_CONF_LISTENER, 0, 0, l->sock);
+ m_add_int(p_frontend, l->proto);
+ m_add_sockaddr(p_frontend, (struct sockaddr *)(&l->ss));
+ m_close(p_frontend);
+ }
+ m_compose(p_frontend, IMSG_CONF_END, 0, 0, -1, NULL, 0);
+}
+
+static void
+priv_dispatch_control(struct imsgproc *proc, struct imsg *imsg, void *arg)
+{
+ if (imsg == NULL)
+ fatalx("%s: imsg connection lost", __func__);
+
+ if (log_getverbose() > LOGLEVEL_IMSG)
+ log_imsg(proc, imsg);
+
+ switch (imsg->hdr.type) {
+ default:
+ fatalx("%s: unexpected imsg %s", __func__,
+ log_fmt_imsgtype(imsg->hdr.type));
+ }
+}
+
+static void
+priv_dispatch_engine(struct imsgproc *proc, struct imsg *imsg, void *arg)
+{
+ const char *prn;
+
+ if (imsg == NULL)
+ fatalx("%s: imsg connection lost", __func__);
+
+ if (log_getverbose() > LOGLEVEL_IMSG)
+ log_imsg(proc, imsg);
+
+ switch (imsg->hdr.type) {
+ case IMSG_LPR_PRINTJOB:
+ m_get_string(proc, &prn);
+ m_end(proc);
+ priv_run_printer(prn);
+ break;
+ default:
+ fatalx("%s: unexpected imsg %s", __func__,
+ log_fmt_imsgtype(imsg->hdr.type));
+ }
+}
+
+static void
+priv_dispatch_frontend(struct imsgproc *proc, struct imsg *imsg, void *arg)
+{
+ if (imsg == NULL)
+ fatalx("%s: imsg connection lost", __func__);
+
+ if (log_getverbose() > LOGLEVEL_IMSG)
+ log_imsg(proc, imsg);
+
+ switch (imsg->hdr.type) {
+ default:
+ fatalx("%s: unexpected imsg %s", __func__,
+ log_fmt_imsgtype(imsg->hdr.type));
+ }
+}
+
+static void
+priv_dispatch_printer(struct imsgproc *proc, struct imsg *imsg, void *arg)
+{
+ if (imsg == NULL) {
+ log_debug("printer process ended, pid=%d, printer=%s",
+ proc_getpid(proc), proc_gettitle(proc));
+ proc_free(proc);
+ return;
+ }
+
+ if (log_getverbose() > LOGLEVEL_IMSG)
+ log_imsg(proc, imsg);
+
+ switch (imsg->hdr.type) {
+ default:
+ fatalx("%s: unexpected imsg %s", __func__,
+ log_fmt_imsgtype(imsg->hdr.type));
+ }
+}
+
+static void
+priv_run_printer(const char *prn)
+{
+ struct imsgproc *p;
+ char **argv, *buf;
+ int argc;
+
+ if (asprintf(&buf, "printer:%s", prn) == -1) {
+ log_warn("%s: asprintf", __func__);
+ return;
+ }
+
+ argv = calloc(saved_argc + 4, sizeof(*argv));
+ if (argv == NULL) {
+ log_warn("%s: calloc", __func__);
+ free(buf);
+ return;
+ }
+ for (argc = 0; argc < saved_argc; argc++)
+ argv[argc] = saved_argv[argc];
+ argv[argc++] = "-X";
+ argv[argc++] = buf;
+ argv[argc++] = NULL;
+
+ p = proc_exec(PROC_PRINTER, argv);
+ if (p == NULL)
+ log_warnx("%s: cannot exec printer process", __func__);
+ else {
+ proc_settitle(p, prn);
+ proc_setcallback(p, priv_dispatch_printer, p);
+ proc_enable(p);
+ }
+
+ free(argv);
+ free(buf);
+}