summaryrefslogtreecommitdiffstats
path: root/usr.sbin/lpd
diff options
context:
space:
mode:
authoreric <eric@openbsd.org>2018-04-27 16:14:35 +0000
committereric <eric@openbsd.org>2018-04-27 16:14:35 +0000
commit3b188dab47094e37e38dcd2dcd35528ab7a95e4d (patch)
tree14225ea843a4fc0f15f460089bded21a34d912fe /usr.sbin/lpd
parentuse nitems() (diff)
downloadwireguard-openbsd-3b188dab47094e37e38dcd2dcd35528ab7a95e4d.tar.xz
wireguard-openbsd-3b188dab47094e37e38dcd2dcd35528ab7a95e4d.zip
Import lpd, a re-implementation of the lpr daemon following the latest
OpenBSD coding practices (fork+exec/privsep/pledge/...). It is only intended to replace the lpd(8) daemon for the moment, not the lpr(1), lprm(1), lpq(1) and lpc(8) commands. This is a work in progress. The server part should be fairly functionnal, but the printer part is not complete: remote printers should work, for local printers it depends on the setup. Anyway, at this point it's better in the tree than rotting on my disk. ok deraadt@
Diffstat (limited to 'usr.sbin/lpd')
-rw-r--r--usr.sbin/lpd/Makefile34
-rw-r--r--usr.sbin/lpd/control.c251
-rw-r--r--usr.sbin/lpd/engine.c173
-rw-r--r--usr.sbin/lpd/engine_lpr.c748
-rw-r--r--usr.sbin/lpd/frontend.c335
-rw-r--r--usr.sbin/lpd/frontend_lpr.c805
-rw-r--r--usr.sbin/lpd/io.c1149
-rw-r--r--usr.sbin/lpd/io.h85
-rw-r--r--usr.sbin/lpd/iobuf.c467
-rw-r--r--usr.sbin/lpd/iobuf.h68
-rw-r--r--usr.sbin/lpd/log.c199
-rw-r--r--usr.sbin/lpd/log.h46
-rw-r--r--usr.sbin/lpd/logmsg.c161
-rw-r--r--usr.sbin/lpd/lp.c932
-rw-r--r--usr.sbin/lpd/lp.h163
-rw-r--r--usr.sbin/lpd/lp_banner.c1153
-rw-r--r--usr.sbin/lpd/lp_displayq.c287
-rw-r--r--usr.sbin/lpd/lp_rmjob.c209
-rw-r--r--usr.sbin/lpd/lp_stty.c543
-rw-r--r--usr.sbin/lpd/lpd.c461
-rw-r--r--usr.sbin/lpd/lpd.h148
-rw-r--r--usr.sbin/lpd/parse.y975
-rw-r--r--usr.sbin/lpd/printer.c1401
-rw-r--r--usr.sbin/lpd/proc.c508
-rw-r--r--usr.sbin/lpd/proc.h57
-rw-r--r--usr.sbin/lpd/resolver.c355
26 files changed, 11713 insertions, 0 deletions
diff --git a/usr.sbin/lpd/Makefile b/usr.sbin/lpd/Makefile
new file mode 100644
index 00000000000..122ee34cbed
--- /dev/null
+++ b/usr.sbin/lpd/Makefile
@@ -0,0 +1,34 @@
+# $OpenBSD: Makefile,v 1.1.1.1 2018/04/27 16:14:35 eric Exp $
+
+PROG= lpd
+
+SRCS+= control.c
+SRCS+= engine.c
+SRCS+= engine_lpr.c
+SRCS+= frontend.c
+SRCS+= frontend_lpr.c
+SRCS+= iobuf.c
+SRCS+= io.c
+SRCS+= log.c
+SRCS+= logmsg.c
+SRCS+= lp.c
+SRCS+= lp_banner.c
+SRCS+= lp_displayq.c
+SRCS+= lp_stty.c
+SRCS+= lp_rmjob.c
+SRCS+= lpd.c
+SRCS+= parse.y
+SRCS+= printer.c
+SRCS+= proc.c
+SRCS+= resolver.c
+
+NOMAN= noman
+BINDIR= /usr/sbin
+
+CFLAGS+= -Wall -I${.CURDIR}
+YFLAGS=
+
+LDADD+= -levent -lutil
+DPADD+= ${LIBEVENT} ${LIBUTIL}
+
+.include <bsd.prog.mk>
diff --git a/usr.sbin/lpd/control.c b/usr.sbin/lpd/control.c
new file mode 100644
index 00000000000..325e21c25ba
--- /dev/null
+++ b/usr.sbin/lpd/control.c
@@ -0,0 +1,251 @@
+/* $OpenBSD: control.c,v 1.1.1.1 2018/04/27 16:14:35 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/queue.h>
+#include <sys/stat.h>
+#include <sys/socket.h>
+#include <sys/un.h>
+
+#include <errno.h>
+#include <event.h>
+#include <imsg.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 priviledges. */
+ if ((pw = getpwnam(LPD_USER)) == NULL)
+ fatalx("unknown user " LPD_USER);
+
+ 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 (chroot(pw->pw_dir) == 1)
+ fatal("%s: chroot", __func__);
+
+ 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);
+ }
+}
diff --git a/usr.sbin/lpd/engine.c b/usr.sbin/lpd/engine.c
new file mode 100644
index 00000000000..b71f63a7163
--- /dev/null
+++ b/usr.sbin/lpd/engine.c
@@ -0,0 +1,173 @@
+/* $OpenBSD: engine.c,v 1.1.1.1 2018/04/27 16:14:35 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 <pwd.h>
+#include <stdlib.h>
+#include <signal.h>
+#include <syslog.h>
+#include <unistd.h>
+
+#include "lpd.h"
+#include "lp.h"
+
+#include "log.h"
+#include "proc.h"
+
+static void engine_shutdown(void);
+static void engine_dispatch_priv(struct imsgproc *, struct imsg *, void *);
+static void engine_dispatch_frontend(struct imsgproc *, struct imsg *, void *);
+
+char *lpd_hostname;
+
+void
+engine(int debug, int verbose)
+{
+ struct passwd *pw;
+
+ /* Early initialisation. */
+ log_init(debug, LOG_LPR);
+ log_setverbose(verbose);
+ log_procinit("engine");
+ setproctitle("engine");
+
+ if ((lpd_hostname = malloc(HOST_NAME_MAX+1)) == NULL)
+ fatal("%s: malloc", __func__);
+ gethostname(lpd_hostname, HOST_NAME_MAX + 1);
+
+ /* Drop priviledges. */
+ if ((pw = getpwnam(LPD_USER)) == NULL)
+ fatal("%s: getpwnam: %s", __func__, LPD_USER);
+
+ 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__);
+
+ /* We need proc for kill(2) in lp_getcurrtask(). */
+ if (pledge("stdio rpath wpath cpath flock dns sendfd recvfd proc",
+ 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, engine_dispatch_priv, NULL);
+ proc_enable(p_priv);
+
+ event_dispatch();
+
+ engine_shutdown();
+}
+
+static void
+engine_shutdown()
+{
+ lpr_shutdown();
+
+ log_debug("exiting");
+
+ exit(0);
+}
+
+static void
+engine_dispatch_priv(struct imsgproc *proc, struct imsg *imsg, void *arg)
+{
+ struct lp_printer lp;
+
+ 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_FRONTEND:
+ m_end(proc);
+
+ if (imsg->fd == -1)
+ fatalx("failed to receive frontend socket");
+
+ p_frontend = proc_attach(PROC_FRONTEND, imsg->fd);
+ proc_setcallback(p_frontend, engine_dispatch_frontend, NULL);
+ proc_enable(p_frontend);
+ break;
+
+ case IMSG_CONF_START:
+ m_end(proc);
+ break;
+
+ case IMSG_CONF_END:
+ m_end(proc);
+
+ /* Fork a printer process for every queue. */
+ while (lp_scanprinters(&lp) == 1) {
+ lpr_printjob(lp.lp_name);
+ lp_clearprinter(&lp);
+ }
+ break;
+
+ default:
+ fatalx("%s: unexpected imsg %s", __func__,
+ log_fmt_imsgtype(imsg->hdr.type));
+ }
+}
+
+static void
+engine_dispatch_frontend(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_RES_GETADDRINFO:
+ case IMSG_RES_GETNAMEINFO:
+ resolver_dispatch_request(proc, imsg);
+ break;
+
+ case IMSG_LPR_ALLOWEDHOST:
+ case IMSG_LPR_DISPLAYQ:
+ case IMSG_LPR_PRINTJOB:
+ case IMSG_LPR_RECVJOB:
+ case IMSG_LPR_RECVJOB_CLEAR:
+ case IMSG_LPR_RECVJOB_CF:
+ case IMSG_LPR_RECVJOB_DF:
+ case IMSG_LPR_RECVJOB_COMMIT:
+ case IMSG_LPR_RECVJOB_ROLLBACK:
+ case IMSG_LPR_RMJOB:
+ lpr_dispatch_frontend(proc, imsg);
+ break;
+
+ default:
+ fatalx("%s: unexpected imsg %s", __func__,
+ log_fmt_imsgtype(imsg->hdr.type));
+ }
+}
diff --git a/usr.sbin/lpd/engine_lpr.c b/usr.sbin/lpd/engine_lpr.c
new file mode 100644
index 00000000000..9d1ee276fad
--- /dev/null
+++ b/usr.sbin/lpd/engine_lpr.c
@@ -0,0 +1,748 @@
+/* $OpenBSD: engine_lpr.c,v 1.1.1.1 2018/04/27 16:14:35 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 <ctype.h>
+#include <errno.h>
+#include <limits.h>
+#include <netgroup.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#include "lpd.h"
+#include "lp.h"
+
+#include "log.h"
+#include "proc.h"
+
+struct lpr_recvfile {
+ TAILQ_ENTRY(lpr_recvfile) entry;
+ char *dfname;
+};
+
+struct lpr_recvjob {
+ TAILQ_ENTRY(lpr_recvjob) entry;
+ struct lp_printer lp;
+ uint32_t connid;
+ char *hostfrom;
+ char *cfname;
+ int dfcount;
+ size_t dfsize;
+ TAILQ_HEAD(, lpr_recvfile) df;
+};
+
+static void lpr_allowedhost(uint32_t, const struct sockaddr *);
+static void lpr_allowedhost_res(uint32_t, const char *, const char *);
+static int lpr_mkstemp(void);
+static void lpr_displayq(uint32_t, const char *, int, struct lp_jobfilter *);
+static void lpr_displayq_res(uint32_t, int, const char *, const char *);
+static void lpr_rmjob(uint32_t, const char *, const char *,
+ struct lp_jobfilter *);
+static void lpr_rmjob_res(uint32_t, int, const char *, const char *);
+static void lpr_recvjob(uint32_t, const char*, const char *);
+static void lpr_recvjob_res(uint32_t, int);
+static void lpr_recvjob_cf(uint32_t, size_t, const char *);
+static void lpr_recvjob_df(uint32_t, size_t, const char *);
+static void lpr_recvjob_clear(uint32_t);
+static void lpr_recvjob_commit(uint32_t);
+static void lpr_recvjob_rollback(uint32_t);
+static void lpr_recvjob_free(struct lpr_recvjob *);
+static int matchaddr(const char *, const struct sockaddr *, int *);
+static int cmpsockaddr(const struct sockaddr *, const struct sockaddr *);
+
+static TAILQ_HEAD(, lpr_recvjob) recvjobs = TAILQ_HEAD_INITIALIZER(recvjobs);
+
+void
+lpr_dispatch_frontend(struct imsgproc *proc, struct imsg *imsg)
+{
+ struct sockaddr_storage ss;
+ struct lp_jobfilter jf;
+ struct lp_printer lp;
+ const char *hostfrom, *prn, *filename, *agent;
+ uint32_t connid;
+ size_t size;
+ int lng, i;
+
+ connid = imsg->hdr.peerid;
+
+ switch (imsg->hdr.type) {
+ case IMSG_LPR_ALLOWEDHOST:
+ m_get_sockaddr(proc, (struct sockaddr *)&ss);
+ m_end(proc);
+ lpr_allowedhost(connid, (struct sockaddr *)&ss);
+ break;
+
+ case IMSG_LPR_DISPLAYQ:
+ memset(&jf, 0, sizeof(jf));
+ m_get_int(proc, &lng);
+ m_get_string(proc, &jf.hostfrom);
+ m_get_string(proc, &prn);
+ m_get_int(proc, &jf.njob);
+ for (i = 0; i < jf.njob; i++)
+ m_get_int(proc, &jf.jobs[i]);
+ m_get_int(proc, &jf.nuser);
+ for (i = 0; i < jf.nuser; i++)
+ m_get_string(proc, &jf.users[i]);
+ m_end(proc);
+ lpr_displayq(connid, prn, lng, &jf);
+ break;
+
+ case IMSG_LPR_PRINTJOB:
+ m_get_string(proc, &prn);
+ m_end(proc);
+ /* Make sure the printer exists. */
+ if (lp_getprinter(&lp, prn) == -1)
+ break;
+ lpr_printjob(lp.lp_name);
+ lp_clearprinter(&lp);
+ break;
+
+ case IMSG_LPR_RECVJOB:
+ m_get_string(proc, &hostfrom);
+ m_get_string(proc, &prn);
+ m_end(proc);
+ lpr_recvjob(connid, hostfrom, prn);
+ break;
+
+ case IMSG_LPR_RECVJOB_CLEAR:
+ m_end(proc);
+ lpr_recvjob_clear(connid);
+ break;
+
+ case IMSG_LPR_RECVJOB_CF:
+ m_get_size(proc, &size);
+ m_get_string(proc, &filename);
+ m_end(proc);
+ lpr_recvjob_cf(connid, size, filename);
+ break;
+
+ case IMSG_LPR_RECVJOB_DF:
+ m_get_size(proc, &size);
+ m_get_string(proc, &filename);
+ m_end(proc);
+ lpr_recvjob_df(connid, size, filename);
+ break;
+
+ case IMSG_LPR_RECVJOB_COMMIT:
+ m_end(proc);
+ lpr_recvjob_commit(connid);
+ break;
+
+ case IMSG_LPR_RECVJOB_ROLLBACK:
+ m_end(proc);
+ lpr_recvjob_rollback(connid);
+ break;
+
+ case IMSG_LPR_RMJOB:
+ memset(&jf, 0, sizeof(jf));
+ m_get_string(proc, &jf.hostfrom);
+ m_get_string(proc, &prn);
+ m_get_string(proc, &agent);
+ m_get_int(proc, &jf.njob);
+ for (i = 0; i < jf.njob; i++)
+ m_get_int(proc, &jf.jobs[i]);
+ m_get_int(proc, &jf.nuser);
+ for (i = 0; i < jf.nuser; i++)
+ m_get_string(proc, &jf.users[i]);
+ m_end(proc);
+ lpr_rmjob(connid, prn, agent, &jf);
+ break;
+
+ default:
+ fatalx("%s: unexpected imsg %s", __func__,
+ log_fmt_imsgtype(imsg->hdr.type));
+ }
+}
+
+void
+lpr_shutdown()
+{
+ struct lpr_recvjob *j;
+
+ /* Cleanup incoming jobs. */
+ while ((j = TAILQ_FIRST(&recvjobs))) {
+ lpr_recvjob_clear(j->connid);
+ lpr_recvjob_free(j);
+ }
+}
+
+void
+lpr_printjob(const char *prn)
+{
+ m_create(p_priv, IMSG_LPR_PRINTJOB, 0, 0, -1);
+ m_add_string(p_priv, prn);
+ m_close(p_priv);
+}
+
+static void
+lpr_allowedhost(uint32_t connid, const struct sockaddr *sa)
+{
+ FILE *fp;
+ size_t linesz = 0;
+ ssize_t linelen;
+ char host[NI_MAXHOST], addr[NI_MAXHOST], serv[NI_MAXSERV];
+ char dom[NI_MAXHOST], *lp, *ep, *line = NULL;
+ int e, rev = 0, ok = 0;
+
+ /* Always accept local connections. */
+ if (sa->sa_family == AF_UNIX) {
+ lpr_allowedhost_res(connid, lpd_hostname, NULL);
+ return;
+ }
+
+ host[0] = '\0';
+
+ /* Print address. */
+ if ((e = getnameinfo(sa, sa->sa_len, addr, sizeof(addr), serv,
+ sizeof(serv), NI_NUMERICHOST))) {
+ log_warnx("%s: could not print addr: %s", __func__,
+ gai_strerror(e));
+ lpr_allowedhost_res(connid, host, "Malformed address");
+ return;
+ }
+
+ /* Get the hostname for the address. */
+ if ((e = getnameinfo(sa, sa->sa_len, host, sizeof(host), NULL, 0,
+ NI_NAMEREQD))) {
+ if (e != EAI_NONAME)
+ log_warnx("%s: could not resolve %s: %s", __func__,
+ addr, gai_strerror(e));
+ lpr_allowedhost_res(connid, host,
+ "No hostname found for your address");
+ return;
+ }
+
+ /* Check for a valid DNS roundtrip. */
+ if (!matchaddr(host, sa, &e)) {
+ if (e)
+ log_warnx("%s: getaddrinfo: %s: %s", __func__,
+ host, gai_strerror(e));
+ lpr_allowedhost_res(connid, host, e ?
+ "Cannot resolve your hostname" :
+ "Your hostname and your address do not match");
+ return;
+ }
+
+ /* Scan the hosts.lpd file. */
+ if ((fp = fopen(_PATH_HOSTSLPD, "r")) == NULL) {
+ log_warn("%s: %s", __func__, _PATH_HOSTSLPD);
+ lpr_allowedhost_res(connid, host,
+ "Cannot access " _PATH_HOSTSLPD);
+ return;
+ }
+
+ dom[0] = '\0';
+ while ((linelen = getline(&line, &linesz, fp)) != -1) {
+ /* Drop comment and strip line. */
+ for (lp = line; *lp; lp++)
+ if (!isspace((unsigned char)*lp))
+ break;
+ if (*lp == '#' || *lp == '\0')
+ continue;
+ for (ep = lp + 1; *ep; ep++)
+ if (isspace((unsigned char)*ep) || *ep == '#') {
+ *ep = '\0';
+ break;
+ }
+
+ rev = 0;
+ switch (lp[0]) {
+ case '-':
+ case '+':
+ switch (lp[1]) {
+ case '\0':
+ ok = 1;
+ break;
+ case '@':
+ if (dom[0] == '\0')
+ getdomainname(dom, sizeof(dom));
+ ok = innetgr(lp + 2, host, NULL, dom);
+ break;
+ default:
+ ok = matchaddr(lp + 1, sa, NULL);
+ break;
+ }
+ if (lp[0] == '-')
+ ok = -ok;
+ break;
+ default:
+ ok = matchaddr(lp, sa, NULL);
+ break;
+ }
+ if (ok)
+ break;
+ }
+
+ free(line);
+ fclose(fp);
+
+ lpr_allowedhost_res(connid, host,
+ (ok > 0) ? NULL : "Access denied");
+}
+
+static void
+lpr_allowedhost_res(uint32_t connid, const char *hostname, const char *reject)
+{
+ m_create(p_frontend, IMSG_LPR_ALLOWEDHOST, connid, 0, -1);
+ m_add_string(p_frontend, hostname);
+ m_add_string(p_frontend, reject ? reject : "");
+ m_close(p_frontend);
+}
+
+static int
+matchaddr(const char *host, const struct sockaddr *sa, int *gaierrno)
+{
+ struct addrinfo hints, *res, *r;
+ int e, ok = 0;
+
+ memset(&hints, 0, sizeof(hints));
+ hints.ai_family = sa->sa_family;
+ hints.ai_socktype = SOCK_DGRAM; /*dummy*/
+ if ((e = getaddrinfo(host, NULL, &hints, &res))) {
+ if (gaierrno)
+ *gaierrno = e;
+ return 0;
+ }
+ if (gaierrno)
+ *gaierrno = 0;
+
+ for (r = res; r; r = r->ai_next)
+ if (cmpsockaddr(sa, r->ai_addr) == 0) {
+ ok = 1;
+ break;
+ }
+ freeaddrinfo(res);
+
+ return ok;
+}
+
+static int
+cmpsockaddr(const struct sockaddr *a, const struct sockaddr *b)
+{
+ const void *aa, *ab;
+ size_t l;
+
+ if (a->sa_family != b->sa_family)
+ return (a->sa_family < b->sa_family) ? -1 : 1;
+
+ switch (a->sa_family) {
+ case AF_UNIX:
+ return 0;
+
+ case AF_INET:
+ aa = &(((const struct sockaddr_in*)a)->sin_addr);
+ ab = &(((const struct sockaddr_in*)b)->sin_addr);
+ l = sizeof(((const struct sockaddr_in*)a)->sin_addr);
+ return memcmp(aa, ab, l);
+
+ case AF_INET6:
+ aa = &(((const struct sockaddr_in6*)a)->sin6_addr);
+ ab = &(((const struct sockaddr_in6*)b)->sin6_addr);
+ l = sizeof(((const struct sockaddr_in*)a)->sin_addr);
+ return memcmp(aa, ab, l);
+
+ }
+
+ return 0;
+}
+
+static int
+lpr_mkstemp(void)
+{
+ char path[PATH_MAX];
+ int fd;
+
+ if (strlcpy(path, _PATH_TMP "lpd.XXXXXXXXXX", sizeof(path)) >=
+ sizeof(path)) {
+ log_warnx("%s: path too long", __func__);
+ return -1;
+ }
+ if ((fd = mkstemp(path)) == -1) {
+ log_warn("%s: mkstemp", __func__);
+ return -1;
+ }
+ (void)unlink(path);
+ return fd;
+
+}
+
+static void
+lpr_displayq(uint32_t connid, const char *prn, int lng, struct lp_jobfilter *jf)
+{
+ struct lp_printer lp;
+ char cmd[LPR_MAXCMDLEN], buf[16];
+ int fd, i;
+
+ if (lp_getprinter(&lp, prn) == -1) {
+ lpr_displayq_res(connid, -1, NULL, NULL);
+ return;
+ }
+
+ fd = lpr_mkstemp();
+ if (fd != -1) {
+ /* Write formatted queue content into the temporary file. */
+ lp_displayq(fd, &lp, lng, jf);
+ if (lseek(fd, 0, SEEK_SET) == -1)
+ log_warn("%s: lseek", __func__);
+ }
+
+ /* Send the result to frontend. */
+ if (lp.lp_type == PRN_LPR) {
+ snprintf(cmd, sizeof(cmd), "%c%s", lng?'\4':'\3', LP_RP(&lp));
+ for (i = 0; i < jf->nuser; i++) {
+ strlcat(cmd, " ", sizeof(cmd));
+ strlcat(cmd, jf->users[i], sizeof(cmd));
+ }
+ for (i = 0; i < jf->njob; i++) {
+ snprintf(buf, sizeof(buf), " %d", jf->jobs[i]);
+ strlcat(cmd, buf, sizeof(cmd));
+ }
+ lpr_displayq_res(connid, fd, lp.lp_host, cmd);
+ }
+ else
+ lpr_displayq_res(connid, fd, NULL, NULL);
+
+ lp_clearprinter(&lp);
+}
+
+static void
+lpr_displayq_res(uint32_t connid, int fd, const char *host, const char *cmd)
+{
+ m_create(p_frontend, IMSG_LPR_DISPLAYQ, connid, 0, fd);
+ m_add_string(p_frontend, host ? host : "");
+ m_add_string(p_frontend, cmd ? cmd : "");
+ m_close(p_frontend);
+}
+
+static void
+lpr_rmjob(uint32_t connid, const char *prn, const char *agent,
+ struct lp_jobfilter *jf)
+{
+ struct lp_printer lp;
+ char cmd[LPR_MAXCMDLEN], buf[16];
+ int fd, i, restart = 0;
+
+ if (lp_getprinter(&lp, prn) == -1) {
+ lpr_rmjob_res(connid, -1, NULL, NULL);
+ return;
+ }
+
+ fd = lpr_mkstemp();
+ if (fd != -1) {
+ /* Write result to the temporary file. */
+ restart = lp_rmjob(fd, &lp, agent, jf);
+ if (lseek(fd, 0, SEEK_SET) == -1)
+ log_warn("%s: lseek", __func__);
+ }
+
+ /* Send the result to frontend. */
+ if (lp.lp_type == PRN_LPR) {
+ snprintf(cmd, sizeof(cmd), "\5%s %s", LP_RP(&lp), agent);
+ for (i = 0; i < jf->nuser; i++) {
+ strlcat(cmd, " ", sizeof(cmd));
+ strlcat(cmd, jf->users[i], sizeof(cmd));
+ }
+ for (i = 0; i < jf->njob; i++) {
+ snprintf(buf, sizeof(buf), " %d", jf->jobs[i]);
+ strlcat(cmd, buf, sizeof(cmd));
+ }
+ lpr_rmjob_res(connid, fd, lp.lp_host, cmd);
+ }
+ else
+ lpr_rmjob_res(connid, fd, NULL, NULL);
+
+ /* If the printer process was stopped, tell parent to re-spawn one. */
+ if (restart)
+ lpr_printjob(lp.lp_name);
+
+ lp_clearprinter(&lp);
+}
+
+static void
+lpr_rmjob_res(uint32_t connid, int fd, const char *host, const char *cmd)
+{
+ m_create(p_frontend, IMSG_LPR_RMJOB, connid, 0, fd);
+ m_add_string(p_frontend, host ? host : "");
+ m_add_string(p_frontend, cmd ? cmd : "");
+ m_close(p_frontend);
+}
+
+static void
+lpr_recvjob(uint32_t connid, const char *hostfrom, const char *prn)
+{
+ struct lpr_recvjob *j;
+ int qstate;
+
+ if ((j = calloc(1, sizeof(*j))) == NULL) {
+ log_warn("%s: calloc", __func__);
+ goto fail;
+ }
+ if (lp_getprinter(&j->lp, prn) == -1)
+ goto fail;
+
+ /* Make sure queueing is not disabled. */
+ if (lp_getqueuestate(&j->lp, 0, &qstate) == -1) {
+ log_warnx("cannot get queue state");
+ goto fail;
+ }
+ if (qstate & LPQ_QUEUE_OFF)
+ goto fail;
+
+ if ((j->hostfrom = strdup(hostfrom)) == NULL) {
+ log_warn("%s: strdup", __func__);
+ goto fail;
+ }
+
+ j->connid = connid;
+ TAILQ_INIT(&j->df);
+ TAILQ_INSERT_TAIL(&recvjobs, j, entry);
+
+ lpr_recvjob_res(connid, LPR_ACK);
+ return;
+
+ fail:
+ if (j) {
+ lp_clearprinter(&j->lp);
+ free(j->hostfrom);
+ }
+ free(j);
+ lpr_recvjob_res(connid, LPR_NACK);
+}
+
+static void
+lpr_recvjob_res(uint32_t connid, int ack)
+{
+ m_create(p_frontend, IMSG_LPR_RECVJOB, connid, 0, -1);
+ m_add_int(p_frontend, ack);
+ m_close(p_frontend);
+}
+
+static void
+lpr_recvjob_cf(uint32_t connid, size_t size, const char *filename)
+{
+ struct lpr_recvjob *j;
+ char fname[PATH_MAX];
+ int fd;
+
+ fd = -1;
+ TAILQ_FOREACH(j, &recvjobs, entry)
+ if (j->connid == connid)
+ break;
+ if (j == NULL) {
+ log_warnx("invalid job id");
+ goto done;
+ }
+
+ if (j->cfname) {
+ log_warnx("duplicate control file");
+ goto done;
+ }
+
+ if (!lp_validfilename(filename, 1)) {
+ log_warnx("invalid control file name %s", filename);
+ goto done;
+ }
+
+ /* Rewrite file to make sure the hostname is correct. */
+ (void)strlcpy(fname, filename, 7);
+ if (strlcat(fname, j->hostfrom, sizeof(fname)) >= sizeof(fname)) {
+ log_warnx("filename too long");
+ goto done;
+ }
+
+ if ((j->cfname = strdup(fname)) == NULL) {
+ log_warn("%s: stdrup", __func__);
+ goto done;
+ }
+
+ fd = lp_create(&j->lp, 1, size, j->cfname);
+ if (fd == -1) {
+ if (errno == EFBIG || errno == ENOSPC)
+ log_warn("rejected control file");
+ else
+ log_warnx("cannot create control file");
+ free(j->cfname);
+ j->cfname = NULL;
+ }
+
+ done:
+ m_create(p_frontend, IMSG_LPR_RECVJOB_CF, connid, 0, fd);
+ m_add_int(p_frontend, (fd == -1) ? LPR_NACK : LPR_ACK);
+ m_add_size(p_frontend, size);
+ m_close(p_frontend);
+}
+
+static void
+lpr_recvjob_df(uint32_t connid, size_t size, const char *filename)
+{
+ struct lpr_recvfile *f;
+ struct lpr_recvjob *j;
+ int fd;
+
+ fd = -1;
+ TAILQ_FOREACH(j, &recvjobs, entry)
+ if (j->connid == connid)
+ break;
+ if (j == NULL) {
+ log_warnx("invalid job id");
+ goto done;
+ }
+
+ if (!lp_validfilename(filename, 0)) {
+ log_warnx("invalid data file name %s", filename);
+ goto done;
+ }
+
+ if ((f = calloc(1, sizeof(*f))) == NULL) {
+ log_warn("%s: calloc", __func__);
+ goto done;
+ }
+
+ if ((f->dfname = strdup(filename)) == NULL) {
+ log_warn("%s: strdup", __func__);
+ free(f);
+ goto done;
+ }
+
+ fd = lp_create(&j->lp, 0, size, f->dfname);
+ if (fd == -1) {
+ if (errno == EFBIG || errno == ENOSPC)
+ log_warn("rejected data file");
+ else
+ log_warnx("cannot create data file");
+ free(f->dfname);
+ free(f);
+ goto done;
+ }
+
+ j->dfcount += 1;
+ j->dfsize += size;
+ TAILQ_INSERT_TAIL(&j->df, f, entry);
+
+ done:
+ m_create(p_frontend, IMSG_LPR_RECVJOB_DF, connid, 0, fd);
+ m_add_int(p_frontend, (fd == -1) ? LPR_NACK : LPR_ACK);
+ m_add_size(p_frontend, size);
+ m_close(p_frontend);
+}
+
+static void
+lpr_recvjob_clear(uint32_t connid)
+{
+ struct lpr_recvfile *f;
+ struct lpr_recvjob *j;
+
+ TAILQ_FOREACH(j, &recvjobs, entry)
+ if (j->connid == connid)
+ break;
+ if (j == NULL) {
+ log_warnx("invalid job id");
+ return;
+ }
+
+ if (j->cfname) {
+ j->cfname[0] = 'c';
+ if (lp_unlink(&j->lp, j->cfname) == -1)
+ log_warn("cannot unlink %s", j->cfname);
+ j->cfname[0] = 't';
+ if (lp_unlink(&j->lp, j->cfname) == 1)
+ log_warn("cannot unlink %s", j->cfname);
+ free(j->cfname);
+ j->cfname = NULL;
+ }
+
+ while ((f = TAILQ_FIRST(&j->df))) {
+ TAILQ_REMOVE(&j->df, f, entry);
+ if (lp_unlink(&j->lp, f->dfname) == -1)
+ log_warn("cannot unlink %s", f->dfname);
+ free(f->dfname);
+ free(f);
+ }
+}
+
+static void
+lpr_recvjob_commit(uint32_t connid)
+{
+ struct lpr_recvjob *j;
+ int ack;
+
+ ack = LPR_NACK;
+ TAILQ_FOREACH(j, &recvjobs, entry)
+ if (j->connid == connid)
+ break;
+ if (j == NULL) {
+ log_warnx("invalid job id");
+ return;
+ }
+
+ if (!j->cfname) {
+ log_warnx("no control file received from %s", j->hostfrom);
+ lpr_recvjob_clear(connid);
+ lpr_recvjob_free(j);
+ return;
+ }
+
+ if ((lp_commit(&j->lp, j->cfname) == -1)) {
+ log_warn("cannot commit %s", j->cfname);
+ lpr_recvjob_clear(connid);
+ lpr_recvjob_free(j);
+ return;
+ }
+
+ log_info("received job %s printer=%s host=%s files=%d size=%zu",
+ j->cfname, j->lp.lp_name, j->hostfrom, j->dfcount, j->dfsize);
+
+ /* Start the printer. */
+ lpr_printjob(j->lp.lp_name);
+ lpr_recvjob_free(j);
+}
+
+static void
+lpr_recvjob_rollback(uint32_t connid)
+{
+ struct lpr_recvjob *j;
+
+ lpr_recvjob_clear(connid);
+
+ TAILQ_FOREACH(j, &recvjobs, entry)
+ if (j->connid == connid)
+ break;
+ if (j == NULL) {
+ log_warnx("invalid job id");
+ return;
+ }
+ lpr_recvjob_free(j);
+}
+
+static void
+lpr_recvjob_free(struct lpr_recvjob *j)
+{
+ struct lpr_recvfile *f;
+
+ TAILQ_REMOVE(&recvjobs, j, entry);
+ lp_clearprinter(&j->lp);
+ free(j->hostfrom);
+ free(j->cfname);
+ while ((f = TAILQ_FIRST(&j->df))) {
+ TAILQ_REMOVE(&j->df, f, entry);
+ free(f->dfname);
+ free(f);
+ }
+}
diff --git a/usr.sbin/lpd/frontend.c b/usr.sbin/lpd/frontend.c
new file mode 100644
index 00000000000..9ea42183c49
--- /dev/null
+++ b/usr.sbin/lpd/frontend.c
@@ -0,0 +1,335 @@
+/* $OpenBSD: frontend.c,v 1.1.1.1 2018/04/27 16:14:35 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/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 priviledges. */
+ 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_RES_GETADDRINFO:
+ case IMSG_RES_GETADDRINFO_END:
+ case IMSG_RES_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));
+ }
+}
diff --git a/usr.sbin/lpd/frontend_lpr.c b/usr.sbin/lpd/frontend_lpr.c
new file mode 100644
index 00000000000..156e64408f9
--- /dev/null
+++ b/usr.sbin/lpd/frontend_lpr.c
@@ -0,0 +1,805 @@
+/* $OpenBSD: frontend_lpr.c,v 1.1.1.1 2018/04/27 16:14:35 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/socket.h>
+#include <netinet/in.h>
+
+#include <ctype.h>
+#include <errno.h>
+#include <limits.h>
+#include <netdb.h>
+#include <stdarg.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <string.h>
+#include <unistd.h>
+
+#include "lpd.h"
+#include "lp.h"
+
+#include "io.h"
+#include "log.h"
+#include "proc.h"
+
+#define SERVER_TIMEOUT 30000
+#define CLIENT_TIMEOUT 5000
+
+#define MAXARG 50
+
+#define F_ZOMBIE 0x1
+#define F_WAITADDRINFO 0x2
+
+#define STATE_READ_COMMAND 0
+#define STATE_READ_FILE 1
+
+struct lpr_conn {
+ SPLAY_ENTRY(lpr_conn) entry;
+ uint32_t id;
+ char hostname[NI_MAXHOST];
+ struct io *io;
+ int state;
+ int flags;
+ int recvjob;
+ int recvcf;
+ size_t expect;
+ FILE *ofp; /* output file when receiving data */
+ int ifd; /* input file for displayq/rmjob */
+
+ char *cmd;
+ int ai_done;
+ struct addrinfo *ai;
+ struct io *iofwd;
+};
+
+SPLAY_HEAD(lpr_conn_tree, lpr_conn);
+
+static int lpr_conn_cmp(struct lpr_conn *, struct lpr_conn *);
+SPLAY_PROTOTYPE(lpr_conn_tree, lpr_conn, entry, lpr_conn_cmp);
+
+static void lpr_on_allowedhost(struct lpr_conn *, const char *, const char *);
+static void lpr_on_recvjob(struct lpr_conn *, int);
+static void lpr_on_recvjob_file(struct lpr_conn *, int, size_t, int, int);
+static void lpr_on_request(struct lpr_conn *, int, const char *, const char *);
+static void lpr_on_getaddrinfo(void *, int, struct addrinfo *);
+
+static void lpr_io_dispatch(struct io *, int, void *);
+static int lpr_readcommand(struct lpr_conn *);
+static int lpr_readfile(struct lpr_conn *);
+static int lpr_parsejobfilter(struct lpr_conn *, struct lp_jobfilter *,
+ int, char **);
+
+static void lpr_free(struct lpr_conn *);
+static void lpr_close(struct lpr_conn *);
+static void lpr_ack(struct lpr_conn *, char);
+static void lpr_reply(struct lpr_conn *, const char *);
+static void lpr_stream(struct lpr_conn *);
+static void lpr_forward(struct lpr_conn *);
+
+static void lpr_iofwd_dispatch(struct io *, int, void *);
+
+static struct lpr_conn_tree conns;
+
+void
+lpr_init(void)
+{
+ SPLAY_INIT(&conns);
+}
+
+void
+lpr_conn(uint32_t connid, struct listener *l, int sock,
+ const struct sockaddr *sa)
+{
+ struct lpr_conn *conn;
+
+ if ((conn = calloc(1, sizeof(*conn))) == NULL) {
+ log_warn("%s: calloc", __func__);
+ close(sock);
+ frontend_conn_closed(connid);
+ return;
+ }
+ conn->id = connid;
+ conn->ifd = -1;
+ conn->io = io_new();
+ if (conn->io == NULL) {
+ log_warn("%s: io_new", __func__);
+ free(conn);
+ close(sock);
+ frontend_conn_closed(connid);
+ return;
+ }
+ SPLAY_INSERT(lpr_conn_tree, &conns, conn);
+ io_set_callback(conn->io, lpr_io_dispatch, conn);
+ io_set_timeout(conn->io, CLIENT_TIMEOUT);
+ io_set_write(conn->io);
+ io_attach(conn->io, sock);
+
+ conn->state = STATE_READ_COMMAND;
+ m_create(p_engine, IMSG_LPR_ALLOWEDHOST, conn->id, 0, -1);
+ m_add_sockaddr(p_engine, sa);
+ m_close(p_engine);
+}
+
+void
+lpr_dispatch_engine(struct imsgproc *proc, struct imsg *imsg)
+{
+ struct lpr_conn *conn = NULL, key;
+ const char *hostname, *reject, *cmd;
+ size_t sz;
+ int ack, cf = 0;
+
+ key.id = imsg->hdr.peerid;
+ if (key.id) {
+ conn = SPLAY_FIND(lpr_conn_tree, &conns, &key);
+ if (conn == NULL) {
+ log_debug("%08x dead-session", key.id);
+ if (imsg->fd != -1)
+ close(imsg->fd);
+ return;
+ }
+ }
+
+ switch (imsg->hdr.type) {
+ case IMSG_LPR_ALLOWEDHOST:
+ m_get_string(proc, &hostname);
+ m_get_string(proc, &reject);
+ m_end(proc);
+ lpr_on_allowedhost(conn, hostname, reject[0] ? reject : NULL);
+ break;
+
+ case IMSG_LPR_RECVJOB:
+ m_get_int(proc, &ack);
+ m_end(proc);
+ lpr_on_recvjob(conn, ack);
+ break;
+
+ case IMSG_LPR_RECVJOB_CF:
+ cf = 1;
+ case IMSG_LPR_RECVJOB_DF:
+ m_get_int(proc, &ack);
+ m_get_size(proc, &sz);
+ m_end(proc);
+ lpr_on_recvjob_file(conn, ack, sz, cf, imsg->fd);
+ break;
+
+ case IMSG_LPR_DISPLAYQ:
+ case IMSG_LPR_RMJOB:
+ m_get_string(proc, &hostname);
+ m_get_string(proc, &cmd);
+ m_end(proc);
+ lpr_on_request(conn, imsg->fd, hostname[0] ? hostname : NULL,
+ cmd[0] ? cmd : NULL);
+ break;
+
+ default:
+ fatalx("%s: unexpected imsg %s", __func__,
+ log_fmt_imsgtype(imsg->hdr.type));
+ }
+}
+
+static void
+lpr_on_allowedhost(struct lpr_conn *conn, const char *hostname,
+ const char *reject)
+{
+ strlcpy(conn->hostname, hostname, sizeof(conn->hostname));
+ if (reject)
+ lpr_reply(conn, reject);
+ else
+ io_set_read(conn->io);
+}
+
+static void
+lpr_on_recvjob(struct lpr_conn *conn, int ack)
+{
+ if (ack == LPR_ACK)
+ conn->recvjob = 1;
+ else
+ log_debug("%08x recvjob failed", conn->id);
+ lpr_ack(conn, ack);
+}
+
+static void
+lpr_on_recvjob_file(struct lpr_conn *conn, int ack, size_t sz, int cf, int fd)
+{
+ if (ack != LPR_ACK) {
+ lpr_ack(conn, ack);
+ return;
+ }
+
+ if (fd == -1) {
+ log_warnx("%s: failed to get fd", __func__);
+ lpr_ack(conn, LPR_NACK);
+ return;
+ }
+
+ conn->ofp = fdopen(fd, "w");
+ if (conn->ofp == NULL) {
+ log_warn("%s: fdopen", __func__);
+ close(fd);
+ lpr_ack(conn, LPR_NACK);
+ return;
+ }
+
+ conn->expect = sz;
+ if (cf)
+ conn->recvcf = cf;
+ conn->state = STATE_READ_FILE;
+
+ lpr_ack(conn, LPR_ACK);
+}
+
+static void
+lpr_on_request(struct lpr_conn *conn, int fd, const char *hostname,
+ const char *cmd)
+{
+ struct addrinfo hints;
+
+ if (fd == -1) {
+ log_warnx("%s: no fd received", __func__);
+ lpr_close(conn);
+ return;
+ }
+
+ log_debug("%08x stream init", conn->id);
+ conn->ifd = fd;
+
+ /* Prepare for command forwarding if necessary. */
+ if (cmd) {
+ log_debug("%08x forwarding to %s: \\%d%s", conn->id, hostname,
+ cmd[0], cmd + 1);
+ conn->cmd = strdup(cmd);
+ if (conn->cmd == NULL)
+ log_warn("%s: strdup", __func__);
+ else {
+ memset(&hints, 0, sizeof(hints));
+ hints.ai_socktype = SOCK_STREAM;
+ conn->flags |= F_WAITADDRINFO;
+ /*
+ * The callback might run immediatly, so conn->ifd
+ * must be set before, to block lpr_forward().
+ */
+ resolver_getaddrinfo(hostname, "printer", &hints,
+ lpr_on_getaddrinfo, conn);
+ }
+ }
+
+ lpr_stream(conn);
+}
+
+static void
+lpr_on_getaddrinfo(void *arg, int r, struct addrinfo *ai)
+{
+ struct lpr_conn *conn = arg;
+
+ conn->flags &= ~F_WAITADDRINFO;
+ if (conn->flags & F_ZOMBIE) {
+ if (ai)
+ freeaddrinfo(ai);
+ lpr_free(conn);
+ }
+ else {
+ conn->ai_done = 1;
+ conn->ai = ai;
+ lpr_forward(conn);
+ }
+}
+
+static void
+lpr_io_dispatch(struct io *io, int evt, void *arg)
+{
+ struct lpr_conn *conn = arg;
+ int r;
+
+ switch (evt) {
+ case IO_DATAIN:
+ switch(conn->state) {
+ case STATE_READ_COMMAND:
+ r = lpr_readcommand(conn);
+ break;
+ case STATE_READ_FILE:
+ r = lpr_readfile(conn);
+ break;
+ default:
+ fatal("%s: unexpected state %d", __func__, conn->state);
+ }
+
+ if (r == 0)
+ io_set_write(conn->io);
+ return;
+
+ case IO_LOWAT:
+ if (conn->recvjob)
+ io_set_read(conn->io);
+ else if (conn->ifd != -1)
+ lpr_stream(conn);
+ else if (conn->cmd == NULL)
+ lpr_close(conn);
+ return;
+
+ case IO_DISCONNECTED:
+ log_debug("%08x disconnected", conn->id);
+ /*
+ * Some clients don't wait for the last acknowledgment to close
+ * the session. So just consider it is closed normally.
+ */
+ case IO_CLOSED:
+ if (conn->recvcf && conn->state == STATE_READ_COMMAND) {
+ /*
+ * Commit the transaction if we received a control file
+ * and the last file was received correctly.
+ */
+ m_compose(p_engine, IMSG_LPR_RECVJOB_COMMIT, conn->id,
+ 0, -1, NULL, 0);
+ conn->recvjob = 0;
+ }
+ break;
+
+ case IO_TIMEOUT:
+ log_debug("%08x timeout", conn->id);
+ break;
+
+ case IO_ERROR:
+ log_debug("%08x io-error", conn->id);
+ break;
+
+ default:
+ fatalx("%s: unexpected event %d", __func__, evt);
+ }
+
+ lpr_close(conn);
+}
+
+static int
+lpr_readcommand(struct lpr_conn *conn)
+{
+ struct lp_jobfilter jf;
+ size_t count;
+ const char *errstr;
+ char *argv[MAXARG], *line;
+ int i, argc, cmd;
+
+ line = io_getline(conn->io, NULL);
+ if (line == NULL) {
+ if (io_datalen(conn->io) >= LPR_MAXCMDLEN) {
+ lpr_reply(conn, "Request line too long");
+ return 0;
+ }
+ return -1;
+ }
+
+ cmd = line[0];
+ line++;
+
+ if (cmd == 0) {
+ lpr_reply(conn, "No command");
+ return 0;
+ }
+
+ log_debug("%08x cmd \\%d", conn->id, cmd);
+
+ /* Parse the command. */
+ for (argc = 0; argc < MAXARG; ) {
+ argv[argc] = strsep(&line, " \t");
+ if (argv[argc] == NULL)
+ break;
+ if (argv[argc][0] != '\0')
+ argc++;
+ }
+ if (argc == MAXARG) {
+ lpr_reply(conn, "Argument list too long");
+ return 0;
+ }
+
+ if (argc == 0) {
+ lpr_reply(conn, "No queue specified");
+ return 0;
+ }
+
+#define CMD(c) ((int)(c))
+#define SUBCMD(c) (0x100 | (int)(c))
+
+ if (conn->recvjob)
+ cmd |= 0x100;
+ switch (cmd) {
+ case CMD('\1'): /* PRINT <prn> */
+ m_create(p_engine, IMSG_LPR_PRINTJOB, 0, 0, -1);
+ m_add_string(p_engine, argv[0]);
+ m_close(p_engine);
+ lpr_ack(conn, LPR_ACK);
+ return 0;
+
+ case CMD('\2'): /* RECEIVE JOB <prn> */
+ m_create(p_engine, IMSG_LPR_RECVJOB, conn->id, 0, -1);
+ m_add_string(p_engine, conn->hostname);
+ m_add_string(p_engine, argv[0]);
+ m_close(p_engine);
+ return 0;
+
+ case CMD('\3'): /* QUEUE STATE SHORT <prn> [job#...] [user..] */
+ case CMD('\4'): /* QUEUE STATE LONG <prn> [job#...] [user..] */
+ if (lpr_parsejobfilter(conn, &jf, argc - 1, argv + 1) == -1)
+ return 0;
+
+ m_create(p_engine, IMSG_LPR_DISPLAYQ, conn->id, 0, -1);
+ m_add_int(p_engine, (cmd == '\3') ? 0 : 1);
+ m_add_string(p_engine, conn->hostname);
+ m_add_string(p_engine, argv[0]);
+ m_add_int(p_engine, jf.njob);
+ for (i = 0; i < jf.njob; i++)
+ m_add_int(p_engine, jf.jobs[i]);
+ m_add_int(p_engine, jf.nuser);
+ for (i = 0; i < jf.nuser; i++)
+ m_add_string(p_engine, jf.users[i]);
+ m_close(p_engine);
+ return 0;
+
+ case CMD('\5'): /* REMOVE JOBS <prn> <agent> [job#...] [user..] */
+ if (argc < 2) {
+ lpr_reply(conn, "No agent specified");
+ return 0;
+ }
+ if (lpr_parsejobfilter(conn, &jf, argc - 2, argv + 2) == -1)
+ return 0;
+
+ m_create(p_engine, IMSG_LPR_RMJOB, conn->id, 0, -1);
+ m_add_string(p_engine, conn->hostname);
+ m_add_string(p_engine, argv[0]);
+ m_add_string(p_engine, argv[1]);
+ m_add_int(p_engine, jf.njob);
+ for (i = 0; i < jf.njob; i++)
+ m_add_int(p_engine, jf.jobs[i]);
+ m_add_int(p_engine, jf.nuser);
+ for (i = 0; i < jf.nuser; i++)
+ m_add_string(p_engine, jf.users[i]);
+ m_close(p_engine);
+ return 0;
+
+ case SUBCMD('\1'): /* ABORT */
+ m_compose(p_engine, IMSG_LPR_RECVJOB_CLEAR, conn->id, 0, -1,
+ NULL, 0);
+ conn->recvcf = 0;
+ lpr_ack(conn, LPR_ACK);
+ return 0;
+
+ case SUBCMD('\2'): /* CONTROL FILE <size> <filename> */
+ case SUBCMD('\3'): /* DATA FILE <size> <filename> */
+ if (argc != 2) {
+ log_debug("%08x invalid number of argument", conn->id);
+ lpr_ack(conn, LPR_NACK);
+ return 0;
+ }
+ errstr = NULL;
+ count = strtonum(argv[0], 1, LPR_MAXFILESIZE, &errstr);
+ if (errstr) {
+ log_debug("%08x invalid file size: %s", conn->id,
+ strerror(errno));
+ lpr_ack(conn, LPR_NACK);
+ return 0;
+ }
+
+ if (cmd == SUBCMD('\2')) {
+ if (conn->recvcf) {
+ log_debug("%08x cf file already received",
+ conn->id);
+ lpr_ack(conn, LPR_NACK);
+ return 0;
+ }
+ m_create(p_engine, IMSG_LPR_RECVJOB_CF, conn->id, 0,
+ -1);
+ }
+ else
+ m_create(p_engine, IMSG_LPR_RECVJOB_DF, conn->id, 0,
+ -1);
+ m_add_size(p_engine, count);
+ m_add_string(p_engine, argv[1]);
+ m_close(p_engine);
+ return 0;
+
+ default:
+ if (conn->recvjob)
+ lpr_reply(conn, "Protocol error");
+ else
+ lpr_reply(conn, "Illegal service request");
+ return 0;
+ }
+}
+
+static int
+lpr_readfile(struct lpr_conn *conn)
+{
+ size_t len, w;
+ char *data;
+
+ if (conn->expect) {
+ /* Read file content. */
+ data = io_data(conn->io);
+ len = io_datalen(conn->io);
+ if (len > conn->expect)
+ len = conn->expect;
+
+ log_debug("%08x %zu bytes received", conn->id, len);
+
+ w = fwrite(data, 1, len, conn->ofp);
+ if (w != len) {
+ log_warnx("%s: fwrite", __func__);
+ lpr_close(conn);
+ return -1;
+ }
+ io_drop(conn->io, w);
+ conn->expect -= w;
+ if (conn->expect)
+ return -1;
+
+ fclose(conn->ofp);
+ conn->ofp = NULL;
+
+ log_debug("%08x file received", conn->id);
+ }
+
+ /* Try to read '\0'. */
+ len = io_datalen(conn->io);
+ if (len == 0)
+ return -1;
+ data = io_data(conn->io);
+ io_drop(conn->io, 1);
+
+ log_debug("%08x eof %d", conn->id, (int)*data);
+
+ if (*data != '\0') {
+ lpr_close(conn);
+ return -1;
+ }
+
+ conn->state = STATE_READ_COMMAND;
+ lpr_ack(conn, LPR_ACK);
+ return 0;
+}
+
+static int
+lpr_parsejobfilter(struct lpr_conn *conn, struct lp_jobfilter *jf, int argc,
+ char **argv)
+{
+ const char *errstr;
+ char *arg;
+ int i, jobnum;
+
+ memset(jf, 0, sizeof(*jf));
+
+ for (i = 0; i < argc; i++) {
+ arg = argv[i];
+ if (isdigit((unsigned int)arg[0])) {
+ if (jf->njob == LP_MAXREQUESTS) {
+ lpr_reply(conn, "Too many requests");
+ return -1;
+ }
+ errstr = NULL;
+ jobnum = strtonum(arg, 0, INT_MAX, &errstr);
+ if (errstr) {
+ lpr_reply(conn, "Invalid job number");
+ return -1;
+ }
+ jf->jobs[jf->njob++] = jobnum;
+ }
+ else {
+ if (jf->nuser == LP_MAXUSERS) {
+ lpr_reply(conn, "Too many users");
+ return -1;
+ }
+ jf->users[jf->nuser++] = arg;
+ }
+ }
+
+ return 0;
+}
+
+static void
+lpr_free(struct lpr_conn *conn)
+{
+ if ((conn->flags & F_WAITADDRINFO) == 0)
+ free(conn);
+}
+
+static void
+lpr_close(struct lpr_conn *conn)
+{
+ uint32_t connid = conn->id;
+
+ SPLAY_REMOVE(lpr_conn_tree, &conns, conn);
+
+ if (conn->recvjob)
+ m_compose(p_engine, IMSG_LPR_RECVJOB_ROLLBACK, conn->id, 0, -1,
+ NULL, 0);
+
+ io_free(conn->io);
+ free(conn->cmd);
+ if (conn->ofp)
+ fclose(conn->ofp);
+ if (conn->ifd != -1)
+ close(conn->ifd);
+ if (conn->ai)
+ freeaddrinfo(conn->ai);
+ if (conn->iofwd)
+ io_free(conn->iofwd);
+
+ conn->flags |= F_ZOMBIE;
+ lpr_free(conn);
+
+ frontend_conn_closed(connid);
+}
+
+static void
+lpr_ack(struct lpr_conn *conn, char c)
+{
+ if (c == 0)
+ log_debug("%08x ack", conn->id);
+ else
+ log_debug("%08x nack %d", conn->id, (int)c);
+
+ io_write(conn->io, &c, 1);
+}
+
+static void
+lpr_reply(struct lpr_conn *conn, const char *s)
+{
+ log_debug("%08x reply: %s", conn->id, s);
+
+ io_printf(conn->io, "%s\n", s);
+}
+
+/*
+ * Stream reponse file to the client.
+ */
+static void
+lpr_stream(struct lpr_conn *conn)
+{
+ char buf[BUFSIZ];
+ ssize_t r;
+
+ for (;;) {
+ if (io_queued(conn->io) > 65536)
+ return;
+
+ r = read(conn->ifd, buf, sizeof(buf));
+ if (r == -1) {
+ if (errno == EINTR)
+ continue;
+ log_warn("%s: read", __func__);
+ break;
+ }
+
+ if (r == 0) {
+ log_debug("%08x stream done", conn->id);
+ break;
+ }
+ log_debug("%08x stream %zu bytes", conn->id, r);
+
+ if (io_write(conn->io, buf, r) == -1) {
+ log_warn("%s: io_write", __func__);
+ break;
+ }
+ }
+
+ close(conn->ifd);
+ conn->ifd = -1;
+
+ if (conn->cmd)
+ lpr_forward(conn);
+
+ else if (io_queued(conn->io) == 0)
+ lpr_close(conn);
+}
+
+/*
+ * Forward request to the remote printer.
+ */
+static void
+lpr_forward(struct lpr_conn *conn)
+{
+ /*
+ * Do not start forwarding the command if the address is not resolved
+ * or if the local response is still being sent to the client.
+ */
+ if (!conn->ai_done || conn->ifd == -1)
+ return;
+
+ if (conn->ai == NULL) {
+ if (io_queued(conn->io) == 0)
+ lpr_close(conn);
+ return;
+ }
+
+ log_debug("%08x forward start", conn->id);
+
+ conn->iofwd = io_new();
+ if (conn->iofwd == NULL) {
+ log_warn("%s: io_new", __func__);
+ if (io_queued(conn->io) == 0)
+ lpr_close(conn);
+ return;
+ }
+ io_set_callback(conn->iofwd, lpr_iofwd_dispatch, conn);
+ io_set_timeout(conn->io, SERVER_TIMEOUT);
+ io_connect(conn->iofwd, conn->ai);
+ conn->ai = NULL;
+}
+
+static void
+lpr_iofwd_dispatch(struct io *io, int evt, void *arg)
+{
+ struct lpr_conn *conn = arg;
+
+ switch (evt) {
+ case IO_CONNECTED:
+ log_debug("%08x forward connected", conn->id);
+ /* Send the request. */
+ io_print(io, conn->cmd);
+ io_print(io, "\n");
+ io_set_write(io);
+ return;
+
+ case IO_DATAIN:
+ /* Relay. */
+ io_write(conn->io, io_data(io), io_datalen(io));
+ io_drop(io, io_datalen(io));
+ return;
+
+ case IO_LOWAT:
+ /* Read response. */
+ io_set_read(io);
+ return;
+
+ case IO_CLOSED:
+ break;
+
+ case IO_DISCONNECTED:
+ log_debug("%08x forward disconnected", conn->id);
+ break;
+
+ case IO_TIMEOUT:
+ log_debug("%08x forward timeout", conn->id);
+ break;
+
+ case IO_ERROR:
+ log_debug("%08x forward io-error", conn->id);
+ break;
+
+ default:
+ fatalx("%s: unexpected event %d", __func__, evt);
+ }
+
+ log_debug("%08x forward done", conn->id);
+
+ io_free(io);
+ free(conn->cmd);
+ conn->cmd = NULL;
+ conn->iofwd = NULL;
+ if (io_queued(conn->io) == 0)
+ lpr_close(conn);
+}
+
+static int
+lpr_conn_cmp(struct lpr_conn *a, struct lpr_conn *b)
+{
+ if (a->id < b->id)
+ return -1;
+ if (a->id > b->id)
+ return 1;
+ return 0;
+}
+
+SPLAY_GENERATE(lpr_conn_tree, lpr_conn, entry, lpr_conn_cmp);
diff --git a/usr.sbin/lpd/io.c b/usr.sbin/lpd/io.c
new file mode 100644
index 00000000000..795405a11ea
--- /dev/null
+++ b/usr.sbin/lpd/io.c
@@ -0,0 +1,1149 @@
+/* $OpenBSD: io.c,v 1.1.1.1 2018/04/27 16:14:36 eric Exp $ */
+
+/*
+ * Copyright (c) 2012 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/socket.h>
+
+#include <errno.h>
+#include <fcntl.h>
+#include <inttypes.h>
+#include <netdb.h>
+#include <stdlib.h>
+#include <string.h>
+#include <stdio.h>
+#include <unistd.h>
+
+#include "io.h"
+#include "iobuf.h"
+#include "log.h"
+
+#ifdef IO_SSL
+#include <openssl/err.h>
+#include <openssl/ssl.h>
+#endif
+
+enum {
+ IO_STATE_DOWN,
+ IO_STATE_UP,
+ IO_STATE_CONNECT,
+ IO_STATE_CONNECT_TLS,
+ IO_STATE_ACCEPT_TLS
+};
+
+#define IO_PAUSE_IN IO_IN
+#define IO_PAUSE_OUT IO_OUT
+
+#define IO_READ 0x0100
+#define IO_WRITE 0x0200
+#define IO_RW (IO_READ | IO_WRITE)
+#define IO_RESET 0x1000
+#define IO_HELD 0x2000
+
+struct io {
+ int sock;
+ void *arg;
+ void (*cb)(struct io*, int, void *);
+ struct iobuf iobuf;
+ size_t lowat;
+ int timeout;
+ int flags;
+ int state;
+ struct event ev;
+ void *tls;
+ const char *error; /* only valid immediately on callback */
+ struct sockaddr *bind;
+ struct addrinfo *ai; /* for connecting */
+};
+
+static const char* io_strflags(int);
+static const char* io_strevents(short);
+
+static void io_reload(struct io *);
+static void io_reset(struct io *, short, void (*)(int, short, void*));
+static void io_frame_enter(const char *, struct io *, int);
+static void io_frame_leave(struct io *);
+static void io_hold(struct io *);
+static void io_release(struct io *);
+static void io_callback(struct io*, int);
+static void io_dispatch(int, short, void *);
+static void io_dispatch_connect(int, short, void *);
+static int io_connect_next(struct io *);
+
+#ifdef IO_SSL
+void ssl_error(const char *); /* XXX external */
+static const char* io_ssl_error(void);
+static void io_dispatch_accept_tls(int, short, void *);
+static void io_dispatch_connect_tls(int, short, void *);
+static void io_dispatch_read_tls(int, short, void *);
+static void io_dispatch_write_tls(int, short, void *);
+static void io_reload_tls(struct io *io);
+#endif
+
+static struct io *current = NULL;
+static long long unsigned frame = 0;
+static int _io_trace = 0;
+
+static const char *states[] = {
+ "DOWN",
+ "UP",
+ "CONNECT",
+ "CONNECT_TLS",
+ "ACCEPT_TLS"
+};
+
+#define io_debug(args...) do { if (_io_trace) log_debug(args); } while(0)
+#define IO_READING(io) (((io)->flags & IO_RW) != IO_WRITE)
+#define IO_WRITING(io) (((io)->flags & IO_RW) != IO_READ)
+
+void
+io_trace(int on)
+{
+ _io_trace = on;
+}
+
+const char*
+io_strio(struct io *io)
+{
+ static char buf[128];
+ char ssl[128];
+
+ ssl[0] = '\0';
+#ifdef IO_SSL
+ if (io->tls) {
+ (void)snprintf(ssl, sizeof ssl, " ssl=%s:%s:%d",
+ SSL_get_version(io->tls),
+ SSL_get_cipher_name(io->tls),
+ SSL_get_cipher_bits(io->tls, NULL));
+ }
+#endif
+ (void)snprintf(buf, sizeof buf,
+ "<io:%p st=%s, fd=%d to=%d fl=%s%s ib=%zu ob=%zu>",
+ io, states[io->state], io->sock, io->timeout,
+ io_strflags(io->flags), ssl, io_datalen(io), io_queued(io));
+
+ return buf;
+}
+
+const char*
+io_strevent(int evt)
+{
+ static char buf[32];
+
+ switch (evt) {
+ case IO_CONNECTED:
+ return "IO_CONNECTED";
+ case IO_TLSREADY:
+ return "IO_TLSREADY";
+ case IO_DATAIN:
+ return "IO_DATAIN";
+ case IO_LOWAT:
+ return "IO_LOWAT";
+ case IO_CLOSED:
+ return "IO_CLOSED";
+ case IO_DISCONNECTED:
+ return "IO_DISCONNECTED";
+ case IO_TIMEOUT:
+ return "IO_TIMEOUT";
+ case IO_ERROR:
+ return "IO_ERROR";
+ case IO_TLSERROR:
+ return "IO_TLSERROR";
+ default:
+ (void)snprintf(buf, sizeof(buf), "IO_? %d", evt);
+ return buf;
+ }
+}
+
+struct io *
+io_new(void)
+{
+ struct io *io;
+
+ io = calloc(1, sizeof(*io));
+ if (io == NULL)
+ return NULL;
+
+ iobuf_init(&io->iobuf, 0, 0);
+ io->sock = -1;
+ io->timeout = -1;
+
+ return io;
+}
+
+void
+io_free(struct io *io)
+{
+ io_debug("%s(%p)", __func__, io);
+
+ /* the current io is virtually dead */
+ if (io == current)
+ current = NULL;
+
+#ifdef IO_SSL
+ if (io->tls) {
+ SSL_free(io->tls);
+ io->tls = NULL;
+ }
+#endif
+
+ if (io->ai)
+ freeaddrinfo(io->ai);
+ if (event_initialized(&io->ev))
+ event_del(&io->ev);
+ if (io->sock != -1) {
+ (void)close(io->sock);
+ io->sock = -1;
+ }
+
+ iobuf_clear(&io->iobuf);
+ free(io->bind);
+ free(io);
+}
+
+int
+io_set_callback(struct io *io, void(*cb)(struct io *, int, void *), void *arg)
+{
+ io->cb = cb;
+ io->arg = arg;
+
+ return 0;
+}
+
+int
+io_set_bindaddr(struct io *io, const struct sockaddr *sa)
+{
+ struct sockaddr *t;
+
+ if (io->state != IO_STATE_DOWN) {
+ errno = EISCONN;
+ return -1;
+ }
+
+ t = malloc(sa->sa_len);
+ if (t == NULL)
+ return -1;
+ memmove(t, sa, sa->sa_len);
+
+ free(io->bind);
+ io->bind = t;
+
+ return 0;
+}
+
+int
+io_set_bufsize(struct io *io, size_t sz)
+{
+ errno = ENOSYS;
+ return -1;
+}
+
+void
+io_set_timeout(struct io *io, int msec)
+{
+ io_debug("%s(%p, %d)", __func__, io, msec);
+
+ io->timeout = msec;
+}
+
+void
+io_set_lowat(struct io *io, size_t lowat)
+{
+ io_debug("%s(%p, %zu)", __func__, io, lowat);
+
+ io->lowat = lowat;
+}
+
+const char *
+io_error(struct io *io)
+{
+ const char *e;
+
+ e = io->error;
+ io->error = NULL;
+ return e;
+}
+
+int
+io_fileno(struct io *io)
+{
+ return io->sock;
+}
+
+int
+io_attach(struct io *io, int sock)
+{
+ if (io->state != IO_STATE_DOWN) {
+ errno = EISCONN;
+ return -1;
+ }
+
+ io->state = IO_STATE_UP;
+ io->sock = sock;
+ io_reload(io);
+ return 0;
+}
+
+int
+io_detach(struct io *io)
+{
+ errno = ENOSYS;
+ return -1;
+}
+
+int
+io_close(struct io *io)
+{
+ errno = ENOSYS;
+ return -1;
+}
+
+int
+io_connect(struct io *io, struct addrinfo *ai)
+{
+ if (ai == NULL) {
+ errno = EINVAL;
+ fatal("%s", __func__);
+ return -1;
+ }
+
+ if (io->state != IO_STATE_DOWN) {
+ freeaddrinfo(ai);
+ errno = EISCONN;
+ fatal("%s", __func__);
+ return -1;
+ }
+
+ io->ai = ai;
+ return io_connect_next(io);
+}
+
+int
+io_disconnect(struct io *io)
+{
+ errno = ENOSYS;
+ fatal("%s", __func__);
+ return -1;
+}
+
+int
+io_starttls(struct io *io, void *ssl)
+{
+#ifdef IO_SSL
+ int mode;
+
+ mode = io->flags & IO_RW;
+ if (mode == 0 || mode == IO_RW)
+ fatalx("%s: full-duplex or unset", __func__);
+
+ if (io->tls)
+ fatalx("%s: SSL already started", __func__);
+ io->tls = ssl;
+
+ if (SSL_set_fd(io->tls, io->sock) == 0) {
+ ssl_error("io_start_tls:SSL_set_fd");
+ return -1;
+ }
+
+ if (mode == IO_WRITE) {
+ io->state = IO_STATE_CONNECT_TLS;
+ SSL_set_connect_state(io->tls);
+ io_reset(io, EV_WRITE, io_dispatch_connect_tls);
+ } else {
+ io->state = IO_STATE_ACCEPT_TLS;
+ SSL_set_accept_state(io->tls);
+ io_reset(io, EV_READ, io_dispatch_accept_tls);
+ }
+
+ return 0;
+#else
+ errno = ENOSYS;
+ return -1;
+#endif
+}
+
+void
+io_pause(struct io *io, int dir)
+{
+ io_debug("%s(%p, %x)", __func__, io, dir);
+
+ io->flags |= dir & (IO_IN | IO_OUT);
+ io_reload(io);
+}
+
+void
+io_resume(struct io *io, int dir)
+{
+ io_debug("%s(%p, %x)", __func__, io, dir);
+
+ io->flags &= ~(dir & (IO_IN | IO_OUT));
+ io_reload(io);
+}
+
+void
+io_set_read(struct io *io)
+{
+ int mode;
+
+ io_debug("%s(%p)", __func__, io);
+
+ mode = io->flags & IO_RW;
+ if (!(mode == 0 || mode == IO_WRITE))
+ fatalx("%s: full-duplex or reading", __func__);
+
+ io->flags &= ~IO_RW;
+ io->flags |= IO_READ;
+ io_reload(io);
+}
+
+void
+io_set_write(struct io *io)
+{
+ int mode;
+
+ io_debug("%s(%p)", __func__, io);
+
+ mode = io->flags & IO_RW;
+ if (!(mode == 0 || mode == IO_READ))
+ fatalx("%s: full-duplex or writing", __func__);
+
+ io->flags &= ~IO_RW;
+ io->flags |= IO_WRITE;
+ io_reload(io);
+}
+
+int
+io_write(struct io *io, const void *buf, size_t len)
+{
+ int r;
+
+ r = iobuf_queue(&io->iobuf, buf, len);
+
+ io_reload(io);
+
+ return r;
+}
+
+int
+io_writev(struct io *io, const struct iovec *iov, int iovcount)
+{
+ int r;
+
+ r = iobuf_queuev(&io->iobuf, iov, iovcount);
+
+ io_reload(io);
+
+ return r;
+}
+
+int
+io_print(struct io *io, const char *s)
+{
+ return io_write(io, s, strlen(s));
+}
+
+int
+io_printf(struct io *io, const char *fmt, ...)
+{
+ va_list ap;
+ int r;
+
+ va_start(ap, fmt);
+ r = io_vprintf(io, fmt, ap);
+ va_end(ap);
+
+ return r;
+}
+
+int
+io_vprintf(struct io *io, const char *fmt, va_list ap)
+{
+
+ char *buf;
+ int len;
+
+ len = vasprintf(&buf, fmt, ap);
+ if (len == -1)
+ return -1;
+
+ len = io_write(io, buf, len);
+ free(buf);
+
+ return len;
+}
+
+size_t
+io_queued(struct io *io)
+{
+ return iobuf_queued(&io->iobuf);
+}
+
+void *
+io_data(struct io *io)
+{
+ return iobuf_data(&io->iobuf);
+}
+
+size_t
+io_datalen(struct io *io)
+{
+ return iobuf_len(&io->iobuf);
+}
+
+char *
+io_getline(struct io *io, size_t *sz)
+{
+ return iobuf_getline(&io->iobuf, sz);
+}
+
+void
+io_drop(struct io *io, size_t sz)
+{
+ return iobuf_drop(&io->iobuf, sz);
+}
+
+const char*
+io_strflags(int flags)
+{
+ static char buf[64];
+
+ buf[0] = '\0';
+
+ switch (flags & IO_RW) {
+ case 0:
+ (void)strlcat(buf, "rw", sizeof buf);
+ break;
+ case IO_READ:
+ (void)strlcat(buf, "R", sizeof buf);
+ break;
+ case IO_WRITE:
+ (void)strlcat(buf, "W", sizeof buf);
+ break;
+ case IO_RW:
+ (void)strlcat(buf, "RW", sizeof buf);
+ break;
+ }
+
+ if (flags & IO_PAUSE_IN)
+ (void)strlcat(buf, ",F_PI", sizeof buf);
+ if (flags & IO_PAUSE_OUT)
+ (void)strlcat(buf, ",F_PO", sizeof buf);
+
+ return buf;
+}
+
+const char*
+io_strevents(short ev)
+{
+ static char buf[64];
+ char buf2[16];
+ int n;
+
+ n = 0;
+ buf[0] = '\0';
+
+ if (ev == 0) {
+ (void)strlcat(buf, "<NONE>", sizeof(buf));
+ return buf;
+ }
+
+ if (ev & EV_TIMEOUT) {
+ (void)strlcat(buf, "EV_TIMEOUT", sizeof(buf));
+ ev &= ~EV_TIMEOUT;
+ n++;
+ }
+
+ if (ev & EV_READ) {
+ if (n)
+ (void)strlcat(buf, "|", sizeof(buf));
+ (void)strlcat(buf, "EV_READ", sizeof(buf));
+ ev &= ~EV_READ;
+ n++;
+ }
+
+ if (ev & EV_WRITE) {
+ if (n)
+ (void)strlcat(buf, "|", sizeof(buf));
+ (void)strlcat(buf, "EV_WRITE", sizeof(buf));
+ ev &= ~EV_WRITE;
+ n++;
+ }
+
+ if (ev & EV_SIGNAL) {
+ if (n)
+ (void)strlcat(buf, "|", sizeof(buf));
+ (void)strlcat(buf, "EV_SIGNAL", sizeof(buf));
+ ev &= ~EV_SIGNAL;
+ n++;
+ }
+
+ if (ev) {
+ if (n)
+ (void)strlcat(buf, "|", sizeof(buf));
+ (void)strlcat(buf, "EV_?=0x", sizeof(buf));
+ (void)snprintf(buf2, sizeof(buf2), "%hx", ev);
+ (void)strlcat(buf, buf2, sizeof(buf));
+ }
+
+ return buf;
+}
+
+/*
+ * Setup the necessary events as required by the current io state,
+ * honouring duplex mode and i/o pause.
+ */
+static void
+io_reload(struct io *io)
+{
+ short events;
+
+ /* The io will be reloaded at release time. */
+ if (io->flags & IO_HELD)
+ return;
+
+ /* Do nothing if no socket. */
+ if (io->sock == -1)
+ return;
+
+#ifdef IO_SSL
+ if (io->tls) {
+ io_reload_tls(io);
+ return;
+ }
+#endif
+
+ io_debug("%s(%p)", __func__, io);
+
+ events = 0;
+ if (IO_READING(io) && !(io->flags & IO_PAUSE_IN))
+ events = EV_READ;
+ if (IO_WRITING(io) && !(io->flags & IO_PAUSE_OUT) && io_queued(io))
+ events |= EV_WRITE;
+
+ io_reset(io, events, io_dispatch);
+}
+
+static void
+io_reset(struct io *io, short events, void (*dispatch)(int, short, void*))
+{
+ struct timeval tv, *ptv;
+
+ io_debug("%s(%p, %s, %p) -> %s", __func__, io,
+ io_strevents(events), dispatch, io_strio(io));
+
+ /*
+ * Indicate that the event has already been reset so that reload
+ * is not called on frame_leave.
+ */
+ io->flags |= IO_RESET;
+
+ if (event_initialized(&io->ev))
+ event_del(&io->ev);
+
+ /*
+ * The io is paused by the user, so we don't want the timeout to be
+ * effective.
+ */
+ if (events == 0)
+ return;
+
+ event_set(&io->ev, io->sock, events, dispatch, io);
+ if (io->timeout >= 0) {
+ tv.tv_sec = io->timeout / 1000;
+ tv.tv_usec = (io->timeout % 1000) * 1000;
+ ptv = &tv;
+ } else
+ ptv = NULL;
+
+ event_add(&io->ev, ptv);
+}
+
+static void
+io_frame_enter(const char *where, struct io *io, int ev)
+{
+ io_debug("io: BEGIN %llu", frame);
+ io_debug("%s(%s, %s, %s)", __func__, where, io_strevents(ev),
+ io_strio(io));
+
+ if (current)
+ fatalx("%s: interleaved frames", __func__);
+
+ current = io;
+
+ io_hold(io);
+}
+
+static void
+io_frame_leave(struct io *io)
+{
+ io_debug("%s(%llu)", __func__, frame);
+
+ if (current && current != io)
+ fatalx("%s: io mismatch", __func__);
+
+ /* The io has been cleared. */
+ if (current == NULL)
+ goto done;
+
+ /*
+ * TODO: There is a possible optimization there:
+ * In a typical half-duplex request/response scenario,
+ * the io is waiting to read a request, and when done, it queues
+ * the response in the output buffer and goes to write mode.
+ * There, the write event is set and will be triggered in the next
+ * event frame. In most case, the write call could be done
+ * immediately as part of the last read frame, thus avoiding to go
+ * through the event loop machinery. So, as an optimisation, we
+ * could detect that case here and force an event dispatching.
+ */
+
+ /* Reload the io if it has not been reset already. */
+ io_release(io);
+ current = NULL;
+ done:
+ io_debug("io: END %llu", frame);
+
+ frame += 1;
+}
+
+static void
+io_hold(struct io *io)
+{
+ io_debug("%s(%p)", __func__, io);
+
+ if (io->flags & IO_HELD)
+ fatalx("%s: already held", __func__);
+
+ io->flags &= ~IO_RESET;
+ io->flags |= IO_HELD;
+}
+
+static void
+io_release(struct io *io)
+{
+ io_debug("%s(%p)", __func__, io);
+
+ if (!(io->flags & IO_HELD))
+ fatalx("%s: not held", __func__);
+
+ io->flags &= ~IO_HELD;
+ if (!(io->flags & IO_RESET))
+ io_reload(io);
+}
+
+static void
+io_callback(struct io *io, int evt)
+{
+ io_debug("%s(%s, %s)", __func__, io_strio(io), io_strevent(evt));
+
+ io->cb(io, evt, io->arg);
+}
+
+static void
+io_dispatch(int fd, short ev, void *arg)
+{
+ struct io *io = arg;
+ size_t w;
+ ssize_t n;
+ int saved_errno;
+
+ io_frame_enter(__func__, io, ev);
+
+ if (ev == EV_TIMEOUT) {
+ io_callback(io, IO_TIMEOUT);
+ goto leave;
+ }
+
+ if (ev & EV_WRITE && (w = io_queued(io))) {
+ if ((n = iobuf_write(&io->iobuf, io->sock)) < 0) {
+ if (n == IOBUF_WANT_WRITE) /* kqueue bug? */
+ goto read;
+ if (n == IOBUF_CLOSED)
+ io_callback(io, IO_DISCONNECTED);
+ else {
+ log_warn("%s: iobuf_write", __func__);
+ saved_errno = errno;
+ io->error = strerror(errno);
+ errno = saved_errno;
+ io_callback(io, IO_ERROR);
+ }
+ goto leave;
+ }
+ if (w > io->lowat && w - n <= io->lowat)
+ io_callback(io, IO_LOWAT);
+ }
+ read:
+
+ if (ev & EV_READ) {
+ iobuf_normalize(&io->iobuf);
+ if ((n = iobuf_read(&io->iobuf, io->sock)) < 0) {
+ if (n == IOBUF_CLOSED)
+ io_callback(io, IO_DISCONNECTED);
+ else {
+ log_warn("%s: iobuf_read", __func__);
+ saved_errno = errno;
+ io->error = strerror(errno);
+ errno = saved_errno;
+ io_callback(io, IO_ERROR);
+ }
+ goto leave;
+ }
+ if (n)
+ io_callback(io, IO_DATAIN);
+ }
+
+leave:
+ io_frame_leave(io);
+}
+
+static void
+io_dispatch_connect(int fd, short ev, void *arg)
+{
+ struct io *io = arg;
+ socklen_t sl;
+ int r, e;
+
+ io_frame_enter(__func__, io, ev);
+
+ if (ev == EV_TIMEOUT)
+ e = ETIMEDOUT;
+ else {
+ sl = sizeof(e);
+ r = getsockopt(fd, SOL_SOCKET, SO_ERROR, &e, &sl);
+ if (r == -1) {
+ log_warn("%s: getsockopt", __func__);
+ e = errno;
+ }
+ else if (e) {
+ errno = e;
+ log_warn("%s: (connect)", __func__);
+ }
+ }
+
+ if (e == 0) {
+ io->state = IO_STATE_UP;
+ io_callback(io, IO_CONNECTED);
+ goto done;
+ }
+
+ while (io->ai) {
+ r = io_connect_next(io);
+ if (r == 0)
+ goto done;
+ e = errno;
+ }
+
+ (void)close(fd);
+ io->sock = -1;
+ io->error = strerror(e);
+ io->state = IO_STATE_DOWN;
+ io_callback(io, e == ETIMEDOUT ? IO_TIMEOUT : IO_ERROR);
+ done:
+ io_frame_leave(io);
+}
+
+static int
+io_connect_next(struct io *io)
+{
+ struct addrinfo *ai;
+ struct linger l;
+ int saved_errno;
+
+ while ((ai = io->ai)) {
+ io->ai = ai->ai_next;
+ ai->ai_next = NULL;
+ if (ai->ai_socktype == SOCK_STREAM)
+ break;
+ freeaddrinfo(ai);
+ }
+
+ if (ai == NULL) {
+ errno = ESOCKTNOSUPPORT;
+ log_warn("%s", __func__);
+ return -1;
+ }
+
+ if ((io->sock = socket(ai->ai_family, ai->ai_socktype | SOCK_NONBLOCK,
+ 0)) == -1) {
+ log_warn("%s: socket", __func__);
+ goto fail;
+ }
+
+ memset(&l, 0, sizeof(l));
+ if (setsockopt(io->sock, SOL_SOCKET, SO_LINGER, &l, sizeof(l)) == -1) {
+ log_warn("%s: setsockopt", __func__);
+ goto fail;
+ }
+
+ if (io->bind && bind(io->sock, io->bind, io->bind->sa_len) == -1) {
+ log_warn("%s: bind", __func__);
+ goto fail;
+ }
+
+ if (connect(io->sock, ai->ai_addr, ai->ai_addr->sa_len) == -1)
+ if (errno != EINPROGRESS) {
+ log_warn("%s: connect", __func__);
+ goto fail;
+ }
+
+ freeaddrinfo(ai);
+ io->state = IO_STATE_CONNECT;
+ io_reset(io, EV_WRITE, io_dispatch_connect);
+ return 0;
+
+ fail:
+ if (io->sock != -1) {
+ saved_errno = errno;
+ close(io->sock);
+ errno = saved_errno;
+ io->error = strerror(errno);
+ io->sock = -1;
+ }
+ freeaddrinfo(ai);
+ if (io->ai) {
+ freeaddrinfo(io->ai);
+ io->ai = NULL;
+ }
+ return -1;
+}
+
+#ifdef IO_SSL
+
+static const char*
+io_ssl_error(void)
+{
+ static char buf[128];
+ unsigned long e;
+
+ e = ERR_peek_last_error();
+ if (e) {
+ ERR_error_string(e, buf);
+ return buf;
+ }
+
+ return "No SSL error";
+}
+
+static void
+io_dispatch_accept_tls(int fd, short event, void *arg)
+{
+ struct io *io = arg;
+ int e, ret;
+
+ io_frame_enter(__func__, io, event);
+
+ if (event == EV_TIMEOUT) {
+ io_callback(io, IO_TIMEOUT);
+ goto leave;
+ }
+
+ if ((ret = SSL_accept(io->tls)) > 0) {
+ io->state = IO_STATE_UP;
+ io_callback(io, IO_TLSREADY);
+ goto leave;
+ }
+
+ switch ((e = SSL_get_error(io->tls, ret))) {
+ case SSL_ERROR_WANT_READ:
+ io_reset(io, EV_READ, io_dispatch_accept_tls);
+ break;
+ case SSL_ERROR_WANT_WRITE:
+ io_reset(io, EV_WRITE, io_dispatch_accept_tls);
+ break;
+ default:
+ io->error = io_ssl_error();
+ ssl_error("io_dispatch_accept_tls:SSL_accept");
+ io_callback(io, IO_TLSERROR);
+ break;
+ }
+
+ leave:
+ io_frame_leave(io);
+}
+
+static void
+io_dispatch_connect_tls(int fd, short event, void *arg)
+{
+ struct io *io = arg;
+ int e, ret;
+
+ io_frame_enter(__func__, io, event);
+
+ if (event == EV_TIMEOUT) {
+ io_callback(io, IO_TIMEOUT);
+ goto leave;
+ }
+
+ if ((ret = SSL_connect(io->tls)) > 0) {
+ io->state = IO_STATE_UP;
+ io_callback(io, IO_TLSREADY);
+ goto leave;
+ }
+
+ switch ((e = SSL_get_error(io->tls, ret))) {
+ case SSL_ERROR_WANT_READ:
+ io_reset(io, EV_READ, io_dispatch_connect_tls);
+ break;
+ case SSL_ERROR_WANT_WRITE:
+ io_reset(io, EV_WRITE, io_dispatch_connect_tls);
+ break;
+ default:
+ io->error = io_ssl_error();
+ ssl_error("io_dispatch_connect_tls:SSL_connect");
+ io_callback(io, IO_TLSERROR);
+ break;
+ }
+
+ leave:
+ io_frame_leave(io);
+}
+
+static void
+io_dispatch_read_tls(int fd, short event, void *arg)
+{
+ struct io *io = arg;
+ int n, saved_errno;
+
+ io_frame_enter(__func__, io, event);
+
+ if (event == EV_TIMEOUT) {
+ io_callback(io, IO_TIMEOUT);
+ goto leave;
+ }
+
+again:
+ iobuf_normalize(&io->iobuf);
+ switch ((n = iobuf_read_ssl(&io->iobuf, (SSL*)io->tls))) {
+ case IOBUF_WANT_READ:
+ io_reset(io, EV_READ, io_dispatch_read_tls);
+ break;
+ case IOBUF_WANT_WRITE:
+ io_reset(io, EV_WRITE, io_dispatch_read_tls);
+ break;
+ case IOBUF_CLOSED:
+ io_callback(io, IO_DISCONNECTED);
+ break;
+ case IOBUF_ERROR:
+ saved_errno = errno;
+ io->error = strerror(errno);
+ errno = saved_errno;
+ log_warn("%s: iobuf_read_ssl", __func__);
+ io_callback(io, IO_ERROR);
+ break;
+ case IOBUF_SSLERROR:
+ io->error = io_ssl_error();
+ ssl_error("io_dispatch_read_tls:SSL_read");
+ io_callback(io, IO_TLSERROR);
+ break;
+ default:
+ io_debug("%s(...) -> r=%d", __func__, n);
+ io_callback(io, IO_DATAIN);
+ if (current == io && IO_READING(io) && SSL_pending(io->tls))
+ goto again;
+ }
+
+ leave:
+ io_frame_leave(io);
+}
+
+static void
+io_dispatch_write_tls(int fd, short event, void *arg)
+{
+ struct io *io = arg;
+ size_t w2, w;
+ int n, saved_errno;
+
+ io_frame_enter(__func__, io, event);
+
+ if (event == EV_TIMEOUT) {
+ io_callback(io, IO_TIMEOUT);
+ goto leave;
+ }
+
+ w = io_queued(io);
+ switch ((n = iobuf_write_ssl(&io->iobuf, (SSL*)io->tls))) {
+ case IOBUF_WANT_READ:
+ io_reset(io, EV_READ, io_dispatch_write_tls);
+ break;
+ case IOBUF_WANT_WRITE:
+ io_reset(io, EV_WRITE, io_dispatch_write_tls);
+ break;
+ case IOBUF_CLOSED:
+ io_callback(io, IO_DISCONNECTED);
+ break;
+ case IOBUF_ERROR:
+ saved_errno = errno;
+ io->error = strerror(errno);
+ errno = saved_errno;
+ log_warn("%s: iobuf_write_ssl", __func__);
+ io_callback(io, IO_ERROR);
+ break;
+ case IOBUF_SSLERROR:
+ io->error = io_ssl_error();
+ ssl_error("io_dispatch_write_tls:SSL_write");
+ io_callback(io, IO_TLSERROR);
+ break;
+ default:
+ io_debug("%s(...) -> w=%d", __func__, n);
+ w2 = io_queued(io);
+ if (w > io->lowat && w2 <= io->lowat)
+ io_callback(io, IO_LOWAT);
+ break;
+ }
+
+ leave:
+ io_frame_leave(io);
+}
+
+static void
+io_reload_tls(struct io *io)
+{
+ short ev = 0;
+ void (*dispatch)(int, short, void*) = NULL;
+
+ switch (io->state) {
+ case IO_STATE_CONNECT_TLS:
+ ev = EV_WRITE;
+ dispatch = io_dispatch_connect_tls;
+ break;
+ case IO_STATE_ACCEPT_TLS:
+ ev = EV_READ;
+ dispatch = io_dispatch_accept_tls;
+ break;
+ case IO_STATE_UP:
+ ev = 0;
+ if (IO_READING(io) && !(io->flags & IO_PAUSE_IN)) {
+ ev = EV_READ;
+ dispatch = io_dispatch_read_tls;
+ }
+ else if (IO_WRITING(io) && !(io->flags & IO_PAUSE_OUT) &&
+ io_queued(io)) {
+ ev = EV_WRITE;
+ dispatch = io_dispatch_write_tls;
+ }
+ if (!ev)
+ return; /* paused */
+ break;
+ default:
+ fatalx("%s: unexpected state %d", __func__, io->state);
+ }
+
+ io_reset(io, ev, dispatch);
+}
+
+#endif /* IO_SSL */
diff --git a/usr.sbin/lpd/io.h b/usr.sbin/lpd/io.h
new file mode 100644
index 00000000000..abd276eadd9
--- /dev/null
+++ b/usr.sbin/lpd/io.h
@@ -0,0 +1,85 @@
+/* $OpenBSD: io.h,v 1.1.1.1 2018/04/27 16:14:36 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 <event.h>
+
+enum {
+ IO_CONNECTED = 0, /* connection successful */
+ IO_TLSREADY, /* TLS started successfully */
+ IO_DATAIN, /* new data in input buffer */
+ IO_LOWAT, /* output queue running low */
+ IO_CLOSED, /* normally terminated */
+ IO_DISCONNECTED, /* error? */
+ IO_TIMEOUT, /* error? */
+ IO_ERROR, /* details? */
+ IO_TLSERROR, /* XXX - needs more work */
+};
+
+#define IO_IN 0x1
+#define IO_OUT 0x2
+
+struct io;
+
+void io_trace(int);
+const char* io_strio(struct io *);
+const char* io_strevent(int);
+
+/* IO management */
+struct io *io_new(void);
+void io_free(struct io *);
+
+/* IO setup */
+int io_set_callback(struct io *, void(*)(struct io *, int, void *), void *);
+int io_set_bindaddr(struct io *, const struct sockaddr *);
+int io_set_bufsize(struct io *, size_t);
+void io_set_timeout(struct io *, int);
+void io_set_lowat(struct io *, size_t);
+
+/* State retreival */
+const char *io_error(struct io *);
+int io_fileno(struct io *);
+
+/* Connection management */
+int io_attach(struct io *io, int);
+int io_detach(struct io *io);
+int io_close(struct io *io);
+int io_connect(struct io *, struct addrinfo *);
+int io_disconnect(struct io *io);
+int io_starttls(struct io *, void *);
+
+/* Flow control */
+void io_pause(struct io *, int);
+void io_resume(struct io *, int);
+
+/* IO direction */
+void io_set_read(struct io *);
+void io_set_write(struct io *);
+
+/* Output buffering */
+int io_write(struct io *, const void *, size_t);
+int io_writev(struct io *, const struct iovec *, int);
+int io_print(struct io *, const char *);
+int io_printf(struct io *, const char *, ...);
+int io_vprintf(struct io *, const char *, va_list);
+size_t io_queued(struct io *);
+
+/* Buffered input */
+void * io_data(struct io *);
+size_t io_datalen(struct io *);
+char * io_getline(struct io *, size_t *);
+void io_drop(struct io *, size_t);
diff --git a/usr.sbin/lpd/iobuf.c b/usr.sbin/lpd/iobuf.c
new file mode 100644
index 00000000000..bc7afed0c64
--- /dev/null
+++ b/usr.sbin/lpd/iobuf.c
@@ -0,0 +1,467 @@
+/* $OpenBSD: iobuf.c,v 1.1.1.1 2018/04/27 16:14:36 eric Exp $ */
+
+/*
+ * Copyright (c) 2012 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/socket.h>
+#include <sys/uio.h>
+
+#include <errno.h>
+#include <limits.h>
+#include <stdarg.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#ifdef IO_SSL
+#include <openssl/err.h>
+#include <openssl/ssl.h>
+#endif
+
+#include "iobuf.h"
+
+#define IOBUF_MAX 65536
+#define IOBUFQ_MIN 4096
+
+struct ioqbuf *ioqbuf_alloc(struct iobuf *, size_t);
+void iobuf_drain(struct iobuf *, size_t);
+
+int
+iobuf_init(struct iobuf *io, size_t size, size_t max)
+{
+ memset(io, 0, sizeof *io);
+
+ if (max == 0)
+ max = IOBUF_MAX;
+
+ if (size == 0)
+ size = max;
+
+ if (size > max)
+ return (-1);
+
+ if ((io->buf = calloc(size, 1)) == NULL)
+ return (-1);
+
+ io->size = size;
+ io->max = max;
+
+ return (0);
+}
+
+void
+iobuf_clear(struct iobuf *io)
+{
+ struct ioqbuf *q;
+
+ free(io->buf);
+
+ while ((q = io->outq)) {
+ io->outq = q->next;
+ free(q);
+ }
+
+ memset(io, 0, sizeof (*io));
+}
+
+void
+iobuf_drain(struct iobuf *io, size_t n)
+{
+ struct ioqbuf *q;
+ size_t left = n;
+
+ while ((q = io->outq) && left) {
+ if ((q->wpos - q->rpos) > left) {
+ q->rpos += left;
+ left = 0;
+ } else {
+ left -= q->wpos - q->rpos;
+ io->outq = q->next;
+ free(q);
+ }
+ }
+
+ io->queued -= (n - left);
+ if (io->outq == NULL)
+ io->outqlast = NULL;
+}
+
+int
+iobuf_extend(struct iobuf *io, size_t n)
+{
+ char *t;
+
+ if (n > io->max)
+ return (-1);
+
+ if (io->max - io->size < n)
+ return (-1);
+
+ t = recallocarray(io->buf, io->size, io->size + n, 1);
+ if (t == NULL)
+ return (-1);
+
+ io->size += n;
+ io->buf = t;
+
+ return (0);
+}
+
+size_t
+iobuf_left(struct iobuf *io)
+{
+ return io->size - io->wpos;
+}
+
+size_t
+iobuf_space(struct iobuf *io)
+{
+ return io->size - (io->wpos - io->rpos);
+}
+
+size_t
+iobuf_len(struct iobuf *io)
+{
+ return io->wpos - io->rpos;
+}
+
+char *
+iobuf_data(struct iobuf *io)
+{
+ return io->buf + io->rpos;
+}
+
+void
+iobuf_drop(struct iobuf *io, size_t n)
+{
+ if (n >= iobuf_len(io)) {
+ io->rpos = io->wpos = 0;
+ return;
+ }
+
+ io->rpos += n;
+}
+
+char *
+iobuf_getline(struct iobuf *iobuf, size_t *rlen)
+{
+ char *buf;
+ size_t len, i;
+
+ buf = iobuf_data(iobuf);
+ len = iobuf_len(iobuf);
+
+ for (i = 0; i + 1 <= len; i++)
+ if (buf[i] == '\n') {
+ /* Note: the returned address points into the iobuf
+ * buffer. We NUL-end it for convenience, and discard
+ * the data from the iobuf, so that the caller doesn't
+ * have to do it. The data remains "valid" as long
+ * as the iobuf does not overwrite it, that is until
+ * the next call to iobuf_normalize() or iobuf_extend().
+ */
+ iobuf_drop(iobuf, i + 1);
+ len = (i && buf[i - 1] == '\r') ? i - 1 : i;
+ buf[len] = '\0';
+ if (rlen)
+ *rlen = len;
+ return (buf);
+ }
+
+ return (NULL);
+}
+
+void
+iobuf_normalize(struct iobuf *io)
+{
+ if (io->rpos == 0)
+ return;
+
+ if (io->rpos == io->wpos) {
+ io->rpos = io->wpos = 0;
+ return;
+ }
+
+ memmove(io->buf, io->buf + io->rpos, io->wpos - io->rpos);
+ io->wpos -= io->rpos;
+ io->rpos = 0;
+}
+
+ssize_t
+iobuf_read(struct iobuf *io, int fd)
+{
+ ssize_t n;
+
+ n = read(fd, io->buf + io->wpos, iobuf_left(io));
+ if (n == -1) {
+ /* XXX is this really what we want? */
+ if (errno == EAGAIN || errno == EINTR)
+ return (IOBUF_WANT_READ);
+ return (IOBUF_ERROR);
+ }
+ if (n == 0)
+ return (IOBUF_CLOSED);
+
+ io->wpos += n;
+
+ return (n);
+}
+
+struct ioqbuf *
+ioqbuf_alloc(struct iobuf *io, size_t len)
+{
+ struct ioqbuf *q;
+
+ if (len < IOBUFQ_MIN)
+ len = IOBUFQ_MIN;
+
+ if ((q = malloc(sizeof(*q) + len)) == NULL)
+ return (NULL);
+
+ q->rpos = 0;
+ q->wpos = 0;
+ q->size = len;
+ q->next = NULL;
+ q->buf = (char *)(q) + sizeof(*q);
+
+ if (io->outqlast == NULL)
+ io->outq = q;
+ else
+ io->outqlast->next = q;
+ io->outqlast = q;
+
+ return (q);
+}
+
+size_t
+iobuf_queued(struct iobuf *io)
+{
+ return io->queued;
+}
+
+void *
+iobuf_reserve(struct iobuf *io, size_t len)
+{
+ struct ioqbuf *q;
+ void *r;
+
+ if (len == 0)
+ return (NULL);
+
+ if (((q = io->outqlast) == NULL) || q->size - q->wpos <= len) {
+ if ((q = ioqbuf_alloc(io, len)) == NULL)
+ return (NULL);
+ }
+
+ r = q->buf + q->wpos;
+ q->wpos += len;
+ io->queued += len;
+
+ return (r);
+}
+
+int
+iobuf_queue(struct iobuf *io, const void *data, size_t len)
+{
+ void *buf;
+
+ if (len == 0)
+ return (0);
+
+ if ((buf = iobuf_reserve(io, len)) == NULL)
+ return (-1);
+
+ memmove(buf, data, len);
+
+ return (len);
+}
+
+int
+iobuf_queuev(struct iobuf *io, const struct iovec *iov, int iovcnt)
+{
+ int i;
+ size_t len = 0;
+ char *buf;
+
+ for (i = 0; i < iovcnt; i++)
+ len += iov[i].iov_len;
+
+ if ((buf = iobuf_reserve(io, len)) == NULL)
+ return (-1);
+
+ for (i = 0; i < iovcnt; i++) {
+ if (iov[i].iov_len == 0)
+ continue;
+ memmove(buf, iov[i].iov_base, iov[i].iov_len);
+ buf += iov[i].iov_len;
+ }
+
+ return (0);
+
+}
+
+int
+iobuf_fqueue(struct iobuf *io, const char *fmt, ...)
+{
+ va_list ap;
+ int len;
+
+ va_start(ap, fmt);
+ len = iobuf_vfqueue(io, fmt, ap);
+ va_end(ap);
+
+ return (len);
+}
+
+int
+iobuf_vfqueue(struct iobuf *io, const char *fmt, va_list ap)
+{
+ char *buf;
+ int len;
+
+ len = vasprintf(&buf, fmt, ap);
+
+ if (len == -1)
+ return (-1);
+
+ len = iobuf_queue(io, buf, len);
+ free(buf);
+
+ return (len);
+}
+
+ssize_t
+iobuf_write(struct iobuf *io, int fd)
+{
+ struct iovec iov[IOV_MAX];
+ struct ioqbuf *q;
+ int i;
+ ssize_t n;
+
+ i = 0;
+ for (q = io->outq; q ; q = q->next) {
+ if (i >= IOV_MAX)
+ break;
+ iov[i].iov_base = q->buf + q->rpos;
+ iov[i].iov_len = q->wpos - q->rpos;
+ i++;
+ }
+
+ n = writev(fd, iov, i);
+ if (n == -1) {
+ if (errno == EAGAIN || errno == EINTR)
+ return (IOBUF_WANT_WRITE);
+ if (errno == EPIPE)
+ return (IOBUF_CLOSED);
+ return (IOBUF_ERROR);
+ }
+
+ iobuf_drain(io, n);
+
+ return (n);
+}
+
+int
+iobuf_flush(struct iobuf *io, int fd)
+{
+ ssize_t s;
+
+ while (io->queued)
+ if ((s = iobuf_write(io, fd)) < 0)
+ return (s);
+
+ return (0);
+}
+
+#ifdef IO_SSL
+
+int
+iobuf_flush_ssl(struct iobuf *io, void *ssl)
+{
+ ssize_t s;
+
+ while (io->queued)
+ if ((s = iobuf_write_ssl(io, ssl)) < 0)
+ return (s);
+
+ return (0);
+}
+
+ssize_t
+iobuf_write_ssl(struct iobuf *io, void *ssl)
+{
+ struct ioqbuf *q;
+ int r;
+ ssize_t n;
+
+ q = io->outq;
+ n = SSL_write(ssl, q->buf + q->rpos, q->wpos - q->rpos);
+ if (n <= 0) {
+ switch ((r = SSL_get_error(ssl, n))) {
+ case SSL_ERROR_WANT_READ:
+ return (IOBUF_WANT_READ);
+ case SSL_ERROR_WANT_WRITE:
+ return (IOBUF_WANT_WRITE);
+ case SSL_ERROR_ZERO_RETURN: /* connection closed */
+ return (IOBUF_CLOSED);
+ case SSL_ERROR_SYSCALL:
+ if (ERR_peek_last_error())
+ return (IOBUF_SSLERROR);
+ if (r == 0)
+ errno = EPIPE;
+ return (IOBUF_ERROR);
+ default:
+ return (IOBUF_SSLERROR);
+ }
+ }
+ iobuf_drain(io, n);
+
+ return (n);
+}
+
+ssize_t
+iobuf_read_ssl(struct iobuf *io, void *ssl)
+{
+ ssize_t n;
+ int r;
+
+ n = SSL_read(ssl, io->buf + io->wpos, iobuf_left(io));
+ if (n < 0) {
+ switch ((r = SSL_get_error(ssl, n))) {
+ case SSL_ERROR_WANT_READ:
+ return (IOBUF_WANT_READ);
+ case SSL_ERROR_WANT_WRITE:
+ return (IOBUF_WANT_WRITE);
+ case SSL_ERROR_SYSCALL:
+ if (ERR_peek_last_error())
+ return (IOBUF_SSLERROR);
+ if (r == 0)
+ errno = EPIPE;
+ return (IOBUF_ERROR);
+ default:
+ return (IOBUF_SSLERROR);
+ }
+ } else if (n == 0)
+ return (IOBUF_CLOSED);
+
+ io->wpos += n;
+
+ return (n);
+}
+
+#endif /* IO_SSL */
diff --git a/usr.sbin/lpd/iobuf.h b/usr.sbin/lpd/iobuf.h
new file mode 100644
index 00000000000..ba33b60e6db
--- /dev/null
+++ b/usr.sbin/lpd/iobuf.h
@@ -0,0 +1,68 @@
+/* $OpenBSD: iobuf.h,v 1.1.1.1 2018/04/27 16:14:36 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.
+ */
+
+struct ioqbuf {
+ struct ioqbuf *next;
+ char *buf;
+ size_t size;
+ size_t wpos;
+ size_t rpos;
+};
+
+struct iobuf {
+ char *buf;
+ size_t max;
+ size_t size;
+ size_t wpos;
+ size_t rpos;
+
+ size_t queued;
+ struct ioqbuf *outq;
+ struct ioqbuf *outqlast;
+};
+
+#define IOBUF_WANT_READ -1
+#define IOBUF_WANT_WRITE -2
+#define IOBUF_CLOSED -3
+#define IOBUF_ERROR -4
+#define IOBUF_SSLERROR -5
+
+int iobuf_init(struct iobuf *, size_t, size_t);
+void iobuf_clear(struct iobuf *);
+
+int iobuf_extend(struct iobuf *, size_t);
+void iobuf_normalize(struct iobuf *);
+void iobuf_drop(struct iobuf *, size_t);
+size_t iobuf_space(struct iobuf *);
+size_t iobuf_len(struct iobuf *);
+size_t iobuf_left(struct iobuf *);
+char *iobuf_data(struct iobuf *);
+char *iobuf_getline(struct iobuf *, size_t *);
+ssize_t iobuf_read(struct iobuf *, int);
+ssize_t iobuf_read_ssl(struct iobuf *, void *);
+
+size_t iobuf_queued(struct iobuf *);
+void* iobuf_reserve(struct iobuf *, size_t);
+int iobuf_queue(struct iobuf *, const void*, size_t);
+int iobuf_queuev(struct iobuf *, const struct iovec *, int);
+int iobuf_fqueue(struct iobuf *, const char *, ...);
+int iobuf_vfqueue(struct iobuf *, const char *, va_list);
+int iobuf_flush(struct iobuf *, int);
+int iobuf_flush_ssl(struct iobuf *, void *);
+ssize_t iobuf_write(struct iobuf *, int);
+ssize_t iobuf_write_ssl(struct iobuf *, void *);
diff --git a/usr.sbin/lpd/log.c b/usr.sbin/lpd/log.c
new file mode 100644
index 00000000000..1eeeecf97a7
--- /dev/null
+++ b/usr.sbin/lpd/log.c
@@ -0,0 +1,199 @@
+/* $OpenBSD: log.c,v 1.1.1.1 2018/04/27 16:14:36 eric Exp $ */
+
+/*
+ * Copyright (c) 2003, 2004 Henning Brauer <henning@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 <stdio.h>
+#include <stdlib.h>
+#include <stdarg.h>
+#include <string.h>
+#include <syslog.h>
+#include <errno.h>
+#include <time.h>
+
+#include "log.h"
+
+static int debug;
+static int verbose;
+static const char *log_procname;
+
+void
+log_init(int n_debug, int facility)
+{
+ extern char *__progname;
+
+ debug = n_debug;
+ verbose = n_debug;
+ log_procinit(__progname);
+
+ if (!debug)
+ openlog(__progname, LOG_PID | LOG_NDELAY, facility);
+
+ tzset();
+}
+
+void
+log_procinit(const char *procname)
+{
+ if (procname != NULL)
+ log_procname = procname;
+}
+
+void
+log_setverbose(int v)
+{
+ verbose = v;
+}
+
+int
+log_getverbose(void)
+{
+ return (verbose);
+}
+
+void
+logit(int pri, const char *fmt, ...)
+{
+ va_list ap;
+
+ va_start(ap, fmt);
+ vlog(pri, fmt, ap);
+ va_end(ap);
+}
+
+void
+vlog(int pri, const char *fmt, va_list ap)
+{
+ char *nfmt;
+ int saved_errno = errno;
+
+ if (debug) {
+ /* best effort in out of mem situations */
+ if (asprintf(&nfmt, "%s\n", fmt) == -1) {
+ vfprintf(stderr, fmt, ap);
+ fprintf(stderr, "\n");
+ } else {
+ vfprintf(stderr, nfmt, ap);
+ free(nfmt);
+ }
+ fflush(stderr);
+ } else
+ vsyslog(pri, fmt, ap);
+
+ errno = saved_errno;
+}
+
+void
+log_warn(const char *emsg, ...)
+{
+ char *nfmt;
+ va_list ap;
+ int saved_errno = errno;
+
+ /* best effort to even work in out of memory situations */
+ if (emsg == NULL)
+ logit(LOG_ERR, "%s", strerror(saved_errno));
+ else {
+ va_start(ap, emsg);
+
+ if (asprintf(&nfmt, "%s: %s", emsg,
+ strerror(saved_errno)) == -1) {
+ /* we tried it... */
+ vlog(LOG_ERR, emsg, ap);
+ logit(LOG_ERR, "%s", strerror(saved_errno));
+ } else {
+ vlog(LOG_ERR, nfmt, ap);
+ free(nfmt);
+ }
+ va_end(ap);
+ }
+
+ errno = saved_errno;
+}
+
+void
+log_warnx(const char *emsg, ...)
+{
+ va_list ap;
+
+ va_start(ap, emsg);
+ vlog(LOG_ERR, emsg, ap);
+ va_end(ap);
+}
+
+void
+log_info(const char *emsg, ...)
+{
+ va_list ap;
+
+ va_start(ap, emsg);
+ vlog(LOG_INFO, emsg, ap);
+ va_end(ap);
+}
+
+void
+log_debug(const char *emsg, ...)
+{
+ va_list ap;
+
+ if (verbose) {
+ va_start(ap, emsg);
+ vlog(LOG_DEBUG, emsg, ap);
+ va_end(ap);
+ }
+}
+
+static void
+vfatalc(int code, const char *emsg, va_list ap)
+{
+ static char s[BUFSIZ];
+ const char *sep;
+
+ if (emsg != NULL) {
+ (void)vsnprintf(s, sizeof(s), emsg, ap);
+ sep = ": ";
+ } else {
+ s[0] = '\0';
+ sep = "";
+ }
+ if (code)
+ logit(LOG_CRIT, "fatal in %s: %s%s%s",
+ log_procname, s, sep, strerror(code));
+ else
+ logit(LOG_CRIT, "fatal in %s%s%s", log_procname, sep, s);
+}
+
+void
+fatal(const char *emsg, ...)
+{
+ va_list ap;
+
+ va_start(ap, emsg);
+ vfatalc(errno, emsg, ap);
+ va_end(ap);
+ exit(1);
+}
+
+void
+fatalx(const char *emsg, ...)
+{
+ va_list ap;
+
+ va_start(ap, emsg);
+ vfatalc(0, emsg, ap);
+ va_end(ap);
+ exit(1);
+}
diff --git a/usr.sbin/lpd/log.h b/usr.sbin/lpd/log.h
new file mode 100644
index 00000000000..6f6b30efebd
--- /dev/null
+++ b/usr.sbin/lpd/log.h
@@ -0,0 +1,46 @@
+/* $OpenBSD: log.h,v 1.1.1.1 2018/04/27 16:14:36 eric Exp $ */
+
+/*
+ * Copyright (c) 2003, 2004 Henning Brauer <henning@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.
+ */
+
+#ifndef LOG_H
+#define LOG_H
+
+#include <stdarg.h>
+#include <sys/cdefs.h>
+
+void log_init(int, int);
+void log_procinit(const char *);
+void log_setverbose(int);
+int log_getverbose(void);
+void log_warn(const char *, ...)
+ __attribute__((__format__ (printf, 1, 2)));
+void log_warnx(const char *, ...)
+ __attribute__((__format__ (printf, 1, 2)));
+void log_info(const char *, ...)
+ __attribute__((__format__ (printf, 1, 2)));
+void log_debug(const char *, ...)
+ __attribute__((__format__ (printf, 1, 2)));
+void logit(int, const char *, ...)
+ __attribute__((__format__ (printf, 2, 3)));
+void vlog(int, const char *, va_list)
+ __attribute__((__format__ (printf, 2, 0)));
+__dead void fatal(const char *, ...)
+ __attribute__((__format__ (printf, 1, 2)));
+__dead void fatalx(const char *, ...)
+ __attribute__((__format__ (printf, 1, 2)));
+
+#endif /* LOG_H */
diff --git a/usr.sbin/lpd/logmsg.c b/usr.sbin/lpd/logmsg.c
new file mode 100644
index 00000000000..8947caa24c8
--- /dev/null
+++ b/usr.sbin/lpd/logmsg.c
@@ -0,0 +1,161 @@
+/* $OpenBSD: logmsg.c,v 1.1.1.1 2018/04/27 16:14:36 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/un.h>
+
+#include <limits.h>
+#include <stdio.h>
+#include <string.h>
+
+#include "lpd.h"
+
+#include "io.h"
+#include "log.h"
+#include "proc.h"
+
+const char *
+log_fmt_proto(int p)
+{
+ switch (p) {
+ case PROTO_LPR:
+ return "lpr";
+ default:
+ return NULL;
+ }
+};
+
+const char *
+log_fmt_imsgtype(int type)
+{
+ static char buf[16];
+
+ switch (type) {
+ case IMSG_NONE:
+ return "IMSG_NONE";
+ case IMSG_SOCK_ENGINE:
+ return "IMSG_SOCK_ENGINE";
+ case IMSG_SOCK_FRONTEND:
+ return "IMSG_SOCK_FRONTEND";
+ case IMSG_CONF_START:
+ return "IMSG_CONF_START";
+ case IMSG_CONF_LISTENER:
+ return "IMSG_CONF_LISTENER";
+ case IMSG_CONF_END:
+ return "IMSG_CONF_END";
+ case IMSG_RES_GETADDRINFO:
+ return "IMSG_RES_GETADDRINFO";
+ case IMSG_RES_GETADDRINFO_END:
+ return "IMSG_RES_GETADDRINFO_END";
+ case IMSG_RES_GETNAMEINFO:
+ return "IMSG_RES_GETNAMEINFO";
+ case IMSG_LPR_ALLOWEDHOST:
+ return "IMSG_LPR_ALLOWEDHOST";
+ case IMSG_LPR_DISPLAYQ:
+ return "IMSG_LPR_DISPLAYQ";
+ case IMSG_LPR_PRINTJOB:
+ return "IMSG_LPR_PRINTJOB";
+ case IMSG_LPR_RECVJOB:
+ return "IMSG_LPR_RECVJOB";
+ case IMSG_LPR_RECVJOB_CLEAR:
+ return "IMSG_LPR_RECVJOB_CLEAR";
+ case IMSG_LPR_RECVJOB_CF:
+ return "IMSG_LPR_RECVJOB_CF";
+ case IMSG_LPR_RECVJOB_DF:
+ return "IMSG_LPR_RECVJOB_DF";
+ case IMSG_LPR_RECVJOB_COMMIT:
+ return "IMSG_LPR_RECVJOB_COMMIT";
+ case IMSG_LPR_RECVJOB_ROLLBACK:
+ return "IMSG_LPR_RECVJOB_ROLLBACK";
+ case IMSG_LPR_RMJOB:
+ return "IMSG_LPR_RMJOB";
+ default:
+ snprintf(buf, sizeof(buf), "?%d", type);
+ return buf;
+ }
+}
+
+const char *
+log_fmt_proctype(int proctype)
+{
+ switch (proctype) {
+ case PROC_CLIENT:
+ return "client";
+ case PROC_CONTROL:
+ return "control";
+ case PROC_ENGINE:
+ return "engine";
+ case PROC_FRONTEND:
+ return "frontend";
+ case PROC_PRINTER:
+ return "printer";
+ case PROC_PRIV:
+ return "priv";
+ default:
+ return NULL;
+ }
+};
+
+const char *
+log_fmt_sockaddr(const struct sockaddr *sa)
+{
+ static char buf[PATH_MAX];
+ char host[NI_MAXHOST], serv[NI_MAXSERV];
+
+ switch (sa->sa_family) {
+ case AF_LOCAL:
+ (void)strlcpy(buf, ((const struct sockaddr_un*)sa)->sun_path,
+ sizeof(buf));
+ return buf;
+
+ case AF_INET:
+ case AF_INET6:
+ if (getnameinfo(sa, sa->sa_len, host, sizeof(host),
+ serv, sizeof(serv), NI_NUMERICHOST | NI_NUMERICSERV)) {
+ log_warnx("%s: getnameinfo", __func__);
+ return NULL;
+ }
+ if (sa->sa_family == AF_INET6)
+ snprintf(buf, sizeof(buf), "[%s]:%s", host, serv);
+ else
+ snprintf(buf, sizeof(buf), "%s:%s", host, serv);
+ return buf;
+
+ default:
+ return NULL;
+ }
+}
+
+void
+log_imsg(struct imsgproc *proc, struct imsg *imsg)
+{
+ if (imsg == NULL)
+ log_debug("imsg src=%s closed",
+ log_fmt_proctype(proc_gettype(proc)));
+ else
+ log_debug("imsg src=%s type=%s len=%d fd=%d",
+ log_fmt_proctype(proc_gettype(proc)),
+ log_fmt_imsgtype(imsg->hdr.type),
+ imsg->hdr.len, imsg->fd);
+}
+
+void
+log_io(const char *name, struct io *io, int ev)
+{
+ log_debug("io %s evt=%s io=%s", name, io_strevent(ev),
+ io_strio(io));
+}
diff --git a/usr.sbin/lpd/lp.c b/usr.sbin/lpd/lp.c
new file mode 100644
index 00000000000..0142cc80d02
--- /dev/null
+++ b/usr.sbin/lpd/lp.c
@@ -0,0 +1,932 @@
+/* $OpenBSD: lp.c,v 1.1.1.1 2018/04/27 16:14:36 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/mount.h>
+#include <sys/stat.h>
+
+#include <ctype.h>
+#include <dirent.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <limits.h>
+#include <signal.h>
+#include <stdarg.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#include "lp.h"
+
+#include "log.h"
+
+#define XXX_PID_MAX 99999
+
+struct qentry {
+ char *name;
+ time_t mtime;
+};
+
+static int readent(struct lp_printer *, char *);
+static int qentrycmp(const void *, const void *);
+static int fullpath(struct lp_printer *, const char *, char *, size_t);
+static int checksize(struct lp_printer *, size_t);
+
+static int scanning;
+static char *db[] = {
+ _PATH_PRINTCAP,
+ NULL,
+};
+
+/*
+ * Fill a printer structure from its /etc/printcap entry.
+ * Return 0 on success, or -1 on error.
+ */
+int
+lp_getprinter(struct lp_printer *lp, const char *name)
+{
+ char *buf = NULL;
+ int r;
+
+ memset(lp, 0, sizeof(*lp));
+
+ r = cgetent(&buf, db, name);
+ if (r == 0)
+ r = readent(lp, buf);
+ free(buf);
+
+ switch (r) {
+ case -3:
+ log_warnx("%s: potential reference loop", name);
+ break;
+ case -2:
+ log_warn("%s", name);
+ break;
+ case -1:
+ log_warnx("%s: unknown printer", name);
+ break;
+ case 0:
+ return 0;
+ case 1:
+ log_warnx("%s: unresolved tc expansion", name);
+ break;
+ default:
+ log_warnx("%s: unexpected return value %d", name, r);
+ }
+
+ lp_clearprinter(lp);
+ return -1;
+}
+
+/*
+ * Iterate over /etc/printcap and fill the printer structure from the next
+ * entry, if any.
+ *
+ * Return 0 if no entry is found.
+ * 1 if a printer is filled.
+ * -1 on error, and set errno.
+ */
+int
+lp_scanprinters(struct lp_printer *lp)
+{
+ char *buf;
+ int r, saved_errno;
+
+ if (scanning++ == 0)
+ r = cgetfirst(&buf, db);
+ else
+ r = cgetnext(&buf, db);
+
+ if (r == 0) {
+ cgetclose();
+ scanning = 0;
+ return 0;
+ }
+ else if (r == 1) {
+ memset(lp, 0, sizeof(*lp));
+ r = readent(lp, buf);
+ free(buf);
+ if (r == -2)
+ goto fail;
+ return 1;
+ }
+ else if (r == -2)
+ errno = ELOOP; /* potential reference loop */
+
+ fail:
+ saved_errno = errno;
+ lp_clearprinter(lp);
+ cgetclose();
+ scanning = 0;
+ errno = saved_errno;
+ return -1;
+}
+
+static int
+readent(struct lp_printer *lp, char *buf)
+{
+ char *s;
+
+ s = strchr(buf, ':');
+ if (s)
+ *s = '\0';
+ lp->lp_name = strdup(buf);
+ if (s)
+ *s = ':';
+ if (lp->lp_name == NULL)
+ return -2;
+
+ s = lp->lp_name;
+ while ((s = strchr(s, '|'))) {
+ *s++ = '\0';
+ if (lp->lp_aliascount < LP_MAXALIASES)
+ lp->lp_aliases[lp->lp_aliascount++] = s;
+ }
+
+#define CGETSTR(x) if (cgetstr(buf, #x, &(lp->lp_ ## x)) == -2) \
+ goto fail
+#define CGETNUM(x, d) if (cgetnum(buf, #x, &(lp->lp_ ## x)) == -1) \
+ (lp->lp_ ## x) = d;
+#define CGETBOOL(x) lp->lp_ ## x = cgetcap(buf, #x, ':') ? 1 : 0
+
+ CGETSTR(af);
+ CGETNUM(br, 0);
+ CGETSTR(cf);
+ CGETSTR(df);
+ CGETSTR(ff);
+ CGETBOOL(fo);
+ CGETSTR(gf);
+ CGETBOOL(hl);
+ CGETBOOL(ic);
+ CGETSTR(if);
+ CGETSTR(lf);
+ CGETSTR(lo);
+ CGETSTR(lp);
+ CGETNUM(mc, 0);
+ CGETSTR(ms);
+ CGETNUM(mx, 0);
+ CGETSTR(nd);
+ CGETSTR(nf);
+ CGETSTR(of);
+ CGETNUM(pc, DEFAULT_PC);
+ CGETNUM(pl, DEFAULT_PL);
+ CGETNUM(pw, DEFAULT_PW);
+ CGETNUM(px, 0);
+ CGETNUM(py, 0);
+ CGETSTR(rf);
+ CGETSTR(rg);
+ CGETSTR(rm);
+ CGETSTR(rp);
+ CGETBOOL(rs);
+ CGETBOOL(rw);
+ CGETBOOL(sb);
+ CGETBOOL(sc);
+ CGETSTR(sd);
+ CGETBOOL(sf);
+ CGETBOOL(sh);
+ CGETSTR(st);
+ CGETSTR(tf);
+ CGETSTR(tr);
+ CGETSTR(vf);
+
+ if (lp->lp_rm && lp->lp_rm[0]) {
+ lp->lp_type = PRN_LPR;
+ lp->lp_host = lp->lp_rm;
+ lp->lp_port = NULL;
+ }
+ else if (strchr(LP_LP(lp), '@')) {
+ lp->lp_type = PRN_NET;
+ lp->lp_port = strdup(LP_LP(lp));
+ lp->lp_host = strchr(lp->lp_port, '@');
+ *lp->lp_host++ = '\0';
+ }
+ else {
+ lp->lp_type = PRN_LOCAL;
+ }
+
+ return 0;
+ fail:
+ return -2;
+}
+
+void
+lp_clearprinter(struct lp_printer *lp)
+{
+ free(lp->lp_name);
+ free(lp->lp_port);
+ if (lp->lp_lock)
+ fclose(lp->lp_lock);
+ free(lp->lp_af);
+ free(lp->lp_cf);
+ free(lp->lp_df);
+ free(lp->lp_ff);
+ free(lp->lp_gf);
+ free(lp->lp_if);
+ free(lp->lp_lf);
+ free(lp->lp_lo);
+ free(lp->lp_lp);
+ free(lp->lp_ms);
+ free(lp->lp_nd);
+ free(lp->lp_nf);
+ free(lp->lp_of);
+ free(lp->lp_rf);
+ free(lp->lp_rg);
+ free(lp->lp_rm);
+ free(lp->lp_rp);
+ free(lp->lp_sd);
+ free(lp->lp_st);
+ free(lp->lp_tf);
+ free(lp->lp_tr);
+ free(lp->lp_vf);
+ memset(lp, 0, sizeof(*lp));
+}
+
+static int
+qentrycmp(const void *aa, const void *bb)
+{
+ const struct qentry *a = aa, *b = bb;
+
+ if (a->mtime < b->mtime)
+ return -1;
+ if (a->mtime > b->mtime)
+ return 1;
+
+ return strcmp(a->name, b->name);
+}
+
+/*
+ * Read the printer queue content.
+ * Return the task count, or -1 on error.
+ */
+int
+lp_readqueue(struct lp_printer *lp, struct lp_queue *q)
+{
+ struct qentry *qe = NULL, *tqe;
+ struct dirent *d;
+ struct stat st;
+ size_t len, sz, nqi, nqe = 0, nqa = 0, n, i;
+ char path[PATH_MAX], *end;
+ DIR *dp= NULL;
+
+ len = strlcpy(path, LP_SD(lp), sizeof(path));
+ if (len == 0 || len >= sizeof(path)) {
+ log_warn("%s: %s: invalid spool directory name",
+ __func__, LP_SD(lp));
+ goto fail;
+ }
+
+ if ((dp = opendir(path)) == NULL) {
+ log_warn("%s: opendir", __func__);
+ goto fail;
+ }
+
+ if (fstat(dirfd(dp), &st) == -1) {
+ log_warn("%s: fstat", __func__);
+ goto fail;
+ }
+ /* Assume half the files are cf files. */
+ nqi = st.st_nlink / 2;
+
+ if (path[len-1] != '/') {
+ len = strlcat(path, "/", sizeof(path));
+ if (len >= sizeof(path)) {
+ errno = ENAMETOOLONG;
+ log_warn("%s: strlcat", __func__);
+ goto fail;
+ }
+ }
+ end = path + len;
+ sz = sizeof(path) - len;
+
+ errno = 0;
+ while ((d = readdir(dp))) {
+ if (d->d_name[0] != 'c' || d->d_name[1] != 'f')
+ continue;
+
+ if (strlen(d->d_name) < 7)
+ continue;
+
+ if (!isdigit((unsigned int)d->d_name[3]) ||
+ !isdigit((unsigned int)d->d_name[4]) ||
+ !isdigit((unsigned int)d->d_name[5]))
+ continue;
+
+ if (strlcpy(end, d->d_name, sz) >= sz) {
+ errno = ENAMETOOLONG;
+ log_warn("%s: strlcat", __func__);
+ goto fail;
+ }
+
+ if (stat(path, &st) == -1) {
+ log_warn("%s: stat", __func__);
+ goto fail;
+ }
+
+ if (nqe == nqa) {
+ n = nqa ? (nqa + 5) : nqi;
+ tqe = recallocarray(qe, nqa, n, sizeof(*qe));
+ if (tqe == NULL) {
+ log_warn("%s: recallocarray", __func__);
+ goto fail;
+ }
+ qe = tqe;
+ nqa = n;
+ }
+
+ if ((qe[nqe].name = strdup(d->d_name)) == NULL) {
+ log_warn("%s: strdup", __func__);
+ goto fail;
+ }
+ qe[nqe].mtime = st.st_mtime;
+ nqe++;
+ }
+ if (errno) {
+ log_warn("%s: readdir", __func__);
+ goto fail;
+ }
+ closedir(dp);
+ dp = NULL;
+
+ qsort(qe, nqe, sizeof(*qe), qentrycmp);
+
+ q->count = nqe;
+ q->cfname = calloc(nqe, sizeof(*q->cfname));
+ if (q->cfname == NULL) {
+ log_warn("%s: calloc", __func__);
+ goto fail;
+ }
+ for (i = 0; i < nqe; i++)
+ q->cfname[i] = qe[i].name;
+
+ free(qe);
+ return nqe;
+
+ fail:
+ if (dp)
+ closedir(dp);
+ for (i = 0; i < nqe; i++)
+ free(qe[i].name);
+ free(qe);
+ lp_clearqueue(q);
+ return -1;
+}
+
+void
+lp_clearqueue(struct lp_queue *q)
+{
+ int i;
+
+ for (i = 0; i < q->count; i++)
+ free(q->cfname[i]);
+ free(q->cfname);
+}
+
+static int
+fullpath(struct lp_printer *lp, const char *fname, char *dst, size_t sz)
+{
+ int r;
+
+ r = snprintf(dst, sz, "%s/%s", LP_SD(lp), fname);
+ if (r < 0 || (size_t)r >= sz) {
+ errno = ENAMETOOLONG;
+ return -1;
+ }
+
+ return 0;
+}
+
+/*
+ * fopen(3) a file in the printer spooldir for reading.
+ */
+FILE *
+lp_fopen(struct lp_printer *lp, const char *fname)
+{
+ char path[PATH_MAX];
+
+ if (fullpath(lp, fname, path, sizeof(path)) == -1)
+ return NULL;
+
+ return fopen(path, "r");
+}
+
+/*
+ * unlink(2) a file in the printer spooldir.
+ */
+int
+lp_unlink(struct lp_printer *lp, const char *fname)
+{
+ char path[PATH_MAX];
+
+ if (fullpath(lp, fname, path, sizeof(path)) == -1)
+ return -1;
+
+ return unlink(path);
+}
+
+/*
+ * stat(2) a file in the printer spooldir.
+ */
+int
+lp_stat(struct lp_printer *lp, const char *fname, struct stat *st)
+{
+ char path[PATH_MAX];
+
+ if (fullpath(lp, fname, path, sizeof(path)) == -1)
+ return -1;
+
+ return stat(path, st);
+}
+
+/*
+ * Grab the lockfile for this printer, and associate it to the printer.
+ * Return -1 on error or 0 otherwise.
+ */
+int
+lp_lock(struct lp_printer *lp)
+{
+ char path[PATH_MAX];
+ struct stat st;
+ int fd, saved_errno;
+
+ if (lp->lp_lock) {
+ errno = EWOULDBLOCK;
+ return -1;
+ }
+
+ if (fullpath(lp, LP_LO(lp), path, sizeof(path)) == -1) {
+ log_warn("%s: %s", __func__, LP_LO(lp));
+ return -1;
+ }
+
+ fd = open(path, O_WRONLY|O_CREAT|O_NOFOLLOW|O_NONBLOCK|O_EXLOCK|O_TRUNC,
+ 0640);
+ if (fd == -1) {
+ if (errno != EWOULDBLOCK)
+ log_warn("%s: open", __func__);
+ return -1;
+ }
+
+ if (fstat(fd, &st) == -1) {
+ log_warn("%s: fstat", __func__);
+ saved_errno = errno;
+ (void)close(fd);
+ errno = saved_errno;
+ return -1;
+ }
+
+ if (!S_ISREG(st.st_mode)) {
+ errno = EFTYPE;
+ log_warnx("%s: %s: Not a regular file", __func__, path);
+ (void)close(fd);
+ return -1;
+ }
+
+ if ((lp->lp_lock = fdopen(fd, "w")) == NULL) {
+ log_warn("%s: fdopen", __func__);
+ saved_errno = errno;
+ (void)close(fd);
+ errno = saved_errno;
+ return -1;
+ }
+
+ lp_setcurrtask(lp, NULL);
+
+ return 0;
+}
+
+/*
+ * Truncate the lock file and close it.
+ */
+void
+lp_unlock(struct lp_printer *lp)
+{
+ if (ftruncate(fileno(lp->lp_lock), 0) == -1)
+ log_warn("%s: ftruncate", __func__);
+ if (fclose(lp->lp_lock) == EOF)
+ log_warn("%s: fclose", __func__);
+ lp->lp_lock = NULL;
+}
+
+int
+lp_getqueuestate(struct lp_printer *lp, int reset, int *qstate)
+{
+ FILE *fp;
+ struct stat st;
+ int saved_errno;
+
+ *qstate = 0;
+
+ fp = lp->lp_lock;
+ if (lp->lp_lock == NULL) {
+ fp = lp_fopen(lp, LP_LO(lp));
+ if (fp == NULL) {
+ if (errno == ENOENT)
+ return 0;
+ log_warn("%s: lp_fopen", __func__);
+ return -1;
+ }
+ }
+
+ if (fstat(fileno(fp), &st) == -1) {
+ log_warn("%s: fstat", __func__);
+ if (lp->lp_lock == NULL) {
+ saved_errno = errno;
+ (void)fclose(fp);
+ errno = saved_errno;
+ }
+ return -1;
+ }
+
+ if (st.st_mode & S_IXUSR)
+ *qstate |= LPQ_PRINTER_DOWN;
+ if (st.st_mode & S_IXGRP)
+ *qstate |= LPQ_QUEUE_OFF;
+ if (st.st_mode & S_IXOTH) {
+ *qstate |= LPQ_QUEUE_UPDATED;
+ if (reset) {
+ st.st_mode &= ~S_IXOTH & 0777;
+ if (fchmod(fileno(lp->lp_lock), st.st_mode) == -1)
+ log_warn("%s: fchmod", __func__);
+ }
+ }
+
+ if (lp->lp_lock == NULL)
+ fclose(fp);
+
+ return 0;
+}
+
+/*
+ * Update the current task description in the lock file.
+ * The lock file must be opened.
+ */
+void
+lp_setcurrtask(struct lp_printer *lp, const char *cfile)
+{
+ int r;
+
+ if (ftruncate(fileno(lp->lp_lock), 0) == -1)
+ log_warn("%s: ftruncate", __func__);
+
+ if (cfile)
+ r = fprintf(lp->lp_lock, "%d\n%s\n", (int)getpid(), cfile);
+ else
+ r = fprintf(lp->lp_lock, "%d\n", (int)getpid());
+ if (r < 0)
+ log_warn("%s: fprintf", __func__);
+
+ if (fflush(lp->lp_lock) != 0)
+ log_warn("%s: fflush", __func__);
+}
+
+/*
+ * Find the pid of the running process if any, and the task being printed.
+ *
+ * Returns -1 on error (errno set).
+ * 0 if no process is running.
+ * 1 if a printer process is up.
+ * 2 if a printer process is up and a file is being printed.
+ */
+int
+lp_getcurrtask(struct lp_printer *lp, pid_t *ppid, char *dst, size_t dstsz)
+{
+ FILE *fp;
+ const char *errstr;
+ char *line = NULL;
+ size_t linesz = 0;
+ ssize_t len;
+ pid_t pid;
+ int r, saved_errno;
+
+ pid = *ppid = 0;
+ dst[0] = '\0';
+ r = -1;
+
+ fp = lp_fopen(lp, LP_LO(lp));
+ if (fp == NULL) {
+ if (errno == ENOENT)
+ return 0;
+ log_warn("%s: lp_fopen", __func__);
+ return -1;
+ }
+
+ while ((len = getline(&line, &linesz, fp)) != -1) {
+ if (line[len-1] == '\n')
+ line[len-1] = '\0';
+
+ /* Read filename. */
+ if (pid) {
+ (void)strlcpy(dst, line, dstsz);
+ break;
+ }
+
+ pid = strtonum(line, 2, XXX_PID_MAX, &errstr);
+ if (errstr) {
+ log_warn("%s: strtonum", __func__);
+ goto done;
+ }
+ }
+
+ if ((errno = ferror(fp))) {
+ log_warn("%s: getline", __func__);
+ goto done;
+ }
+
+ r = 0;
+ if (pid == 0)
+ goto done;
+
+ if (kill(pid, 0) == -1 && errno != EPERM) {
+ if (errno != ESRCH)
+ log_warn("%s: kill", __func__);
+ goto done;
+ }
+
+ *ppid = pid;
+ r = dst[0] ? 2 : 1;
+
+ done:
+ free(line);
+ saved_errno = errno;
+ (void)fclose(fp);
+ errno = saved_errno;
+ return r;
+}
+
+/*
+ * Read the current printer status file.
+ * Return -1 on error, 0 on success.
+ */
+int
+lp_getstatus(struct lp_printer *lp, char *buf, size_t bufsz)
+{
+ size_t len;
+ char *line;
+ FILE *fp;
+ int saved_errno;
+
+ buf[0] = '\0';
+
+ fp = lp_fopen(lp, LP_ST(lp));
+ if (fp == NULL) {
+ if (errno == ENOENT)
+ return 0;
+ log_warn("%s: lp_fopen", __func__);
+ return -1;
+ }
+
+ line = fgetln(fp, &len);
+ if (line) {
+ if (len >= bufsz)
+ len = bufsz - 1;
+ else if (line[len - 1] == '\n')
+ len--;
+ memmove(buf, line, len);
+ buf[len] = '\0';
+ }
+ else if ((errno = ferror(fp))) {
+ log_warn("%s: fgetln", __func__);
+ saved_errno = errno;
+ (void)fclose(fp);
+ errno = saved_errno;
+ return -1;
+ }
+
+ (void)fclose(fp);
+ return 0;
+}
+
+/*
+ * Update the current printer status.
+ */
+void
+lp_setstatus(struct lp_printer *lp, const char *fmt, ...)
+{
+ va_list ap;
+ FILE *fp;
+ char path[PATH_MAX], *s;
+ int fd = -1, r, saved_errno;
+
+ va_start(ap, fmt);
+ r = vasprintf(&s, fmt, ap);
+ va_end(ap);
+
+ if (r == -1) {
+ log_warn("%s: vasprintf", __func__);
+ return;
+ }
+
+ if (fullpath(lp, LP_ST(lp), path, sizeof(path)) == -1) {
+ log_warn("%s: fullpath", __func__);
+ free(s);
+ return;
+ }
+
+ fd = open(path, O_WRONLY|O_CREAT|O_NOFOLLOW|O_EXLOCK|O_TRUNC, 0660);
+ if (fd == -1) {
+ log_warn("%s: open", __func__);
+ free(s);
+ return;
+ }
+
+ fp = fdopen(fd, "w");
+ if (fp == NULL) {
+ log_warn("%s: fdopen", __func__);
+ saved_errno = errno;
+ (void)close(fd);
+ free(s);
+ errno = saved_errno;
+ return;
+ }
+
+ r = fprintf(fp, "%s\n", s);
+
+ if (r <= 0) {
+ log_warn("%s: fprintf", __func__);
+ saved_errno = errno;
+ (void)fclose(fp);
+ free(s);
+ errno = saved_errno;
+ return;
+ }
+
+ (void)fclose(fp);
+ free(s);
+}
+
+/*
+ * Check that the given string is a valid filename for the spooler.
+ */
+int
+lp_validfilename(const char *fname, int cf)
+{
+ size_t len, i;
+
+ len = strlen(fname);
+ if (len <= 6) /* strlen("cfA000") */
+ return 0;
+
+ if (fname[0] != (cf ? 'c' : 'd') ||
+ fname[1] != 'f' ||
+ fname[2] < 'A' ||
+ fname[2] > 'Z' ||
+ !isdigit((unsigned char)fname[3]) ||
+ !isdigit((unsigned char)fname[4]) ||
+ !isdigit((unsigned char)fname[5]))
+ return 0;
+
+ for (i = 6; i < len; i++)
+ if (!isalpha((unsigned char)fname[i]) &&
+ !isdigit((unsigned char)fname[i]) &&
+ strchr("-_.", (unsigned char)fname[i]) == NULL)
+ return 0;
+
+ return 1;
+}
+
+/*
+ * Create a new control or data file in the printer spooldir.
+ * Control files have there name changed to a temporary name. They are renamed
+ * to their final name by lp_commit().
+ */
+int
+lp_create(struct lp_printer *lp, int cf, size_t sz, const char *fname)
+{
+ struct stat st;
+ char path[PATH_MAX];
+ int fd;
+
+ /* Make sure the file size is acceptable. */
+ if (checksize(lp, sz) == -1)
+ return -1;
+
+ if (fullpath(lp, fname, path, sizeof(path)) == -1) {
+ log_warn("%s: %s", __func__, fname);
+ return -1;
+ }
+
+ if (cf) {
+ /*
+ * Create a temporay file, but we want to avoid
+ * a collision with the final cf filename.
+ */
+ /* XXX this would require a lock on .seq */
+ path[strlen(LP_SD(lp)) + 1] = 'c';
+ if (stat(path, &st) == 0) {
+ errno = EEXIST;
+ log_warn("%s: %s", __func__, fname);
+ return -1;
+ }
+ path[strlen(LP_SD(lp)) + 1] = 't';
+ }
+
+ fd = open(path, O_RDWR|O_CREAT|O_EXCL|O_NOFOLLOW, 0660);
+ if (fd == -1)
+ log_warn("%s: open", __func__);
+
+ return fd;
+}
+
+/*
+ * Commit the job given by its temporary CF name.
+ * This is done by renaming the temporay CF file name to its final name.
+ * The functions return 0 on success, or -1 on error and set errno.
+ */
+int
+lp_commit(struct lp_printer *lp, const char *cf)
+{
+ char ipath[PATH_MAX], opath[PATH_MAX];
+
+ if (fullpath(lp, cf, ipath, sizeof(ipath)) == -1 ||
+ fullpath(lp, cf, opath, sizeof(opath)) == -1)
+ return -1;
+
+ ipath[strlen(LP_SD(lp)) + 1] = 't';
+ opath[strlen(LP_SD(lp)) + 1] = 'c';
+
+ return rename(ipath, opath);
+}
+
+/*
+ * Check if a file size is acceptable.
+ * Return -1 on error or if false (EFBIG or ENOSPC), 0 otherwise.
+ */
+static int
+checksize(struct lp_printer *lp, size_t sz)
+{
+ struct statfs st;
+ ssize_t len;
+ size_t linesz = 0;
+ off_t req, avail, minfree;
+ char *line = NULL;
+ const char *errstr;
+ FILE *fp;
+ int saved_errno;
+
+ if (sz == 0) {
+ errno = EINVAL;
+ return -1;
+ }
+
+ /* Check printer limit. */
+ if (lp->lp_mx && sz > (size_t)lp->lp_mx) {
+ errno = EFBIG;
+ return -1;
+ }
+
+ /*
+ * Check for minfree. Note that it does not really guarantee the
+ * directory will not be filled up anyway, since it's not taking
+ * other incoming files into account.
+ */
+ fp = lp_fopen(lp, "minfree");
+ if (fp == NULL) {
+ if (errno == ENOENT)
+ return 0;
+ log_warn("%s: lp_fopen", __func__);
+ return -1;
+ }
+
+ len = getline(&line, &linesz, fp);
+ saved_errno = errno;
+ (void)fclose(fp);
+ if (len == -1) {
+ errno = saved_errno;
+ return 0;
+ }
+
+ if (line[len - 1] == '\n')
+ line[len - 1] = '\0';
+ minfree = strtonum(line, 0, INT_MAX, &errstr);
+ free(line);
+
+ if (errstr)
+ return 0;
+
+ if (statfs(LP_SD(lp), &st) == -1)
+ return 0;
+
+ req = sz / 512 + (sz % 512) ? 1 : 0;
+ avail = st.f_bavail * (st.f_bsize / 512);
+ if (avail < minfree || (avail - minfree) < req) {
+ errno = ENOSPC;
+ return -1;
+ }
+
+ return 0;
+}
diff --git a/usr.sbin/lpd/lp.h b/usr.sbin/lpd/lp.h
new file mode 100644
index 00000000000..340305958ac
--- /dev/null
+++ b/usr.sbin/lpd/lp.h
@@ -0,0 +1,163 @@
+/* $OpenBSD: lp.h,v 1.1.1.1 2018/04/27 16:14:36 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/stat.h>
+
+#include <paths.h>
+#include <stdio.h>
+
+#define _PATH_PRINTCAP "/etc/printcap"
+#define _PATH_HOSTSLPD "/etc/hosts.lpd"
+
+#define DEFAULT_FF "\f"
+#define DEFAULT_LF _PATH_CONSOLE
+#define DEFAULT_LO "lock"
+#define DEFAULT_LP "/dev/lp"
+#define DEFAULT_PC 200
+#define DEFAULT_PL 66
+#define DEFAULT_PW 132
+#define DEFAULT_RP "lp"
+#define DEFAULT_SD "/var/spool/output"
+#define DEFAULT_ST "status"
+
+#define LP_MAXALIASES 32
+#define LP_MAXUSERS 50
+#define LP_MAXREQUESTS 50
+
+#define LPR_ACK 0
+#define LPR_NACK 1 /* only for sending */
+
+#define PRN_LOCAL 0 /* local printer */
+#define PRN_NET 1 /* printer listening directly on a port */
+#define PRN_LPR 2 /* some lpr daemon */
+
+#define LPQ_PRINTER_DOWN 0x1
+#define LPQ_QUEUE_OFF 0x2
+#define LPQ_QUEUE_UPDATED 0x4
+
+#define LP_FF(p) (((p)->lp_ff) ? ((p)->lp_ff) : DEFAULT_FF)
+#define LP_LF(p) (((p)->lp_lf) ? ((p)->lp_lf) : DEFAULT_LF)
+#define LP_LO(p) (((p)->lp_lo) ? ((p)->lp_lo) : DEFAULT_LO)
+#define LP_LP(p) (((p)->lp_lp) ? ((p)->lp_lp) : DEFAULT_LP)
+#define LP_RP(p) (((p)->lp_rp) ? ((p)->lp_rp) : DEFAULT_RP)
+#define LP_SD(p) (((p)->lp_sd) ? ((p)->lp_sd) : DEFAULT_SD)
+#define LP_ST(p) (((p)->lp_st) ? ((p)->lp_st) : DEFAULT_ST)
+
+#define LP_JOBNUM(cf) (100*((cf)[3]-'0') + 10*((cf)[4]-'0') + ((cf)[5]-'0'))
+#define LP_JOBHOST(cf) ((cf) + 6)
+
+struct lp_printer {
+ int lp_type;
+ char *lp_name;
+ char *lp_aliases[LP_MAXALIASES];
+ int lp_aliascount;
+
+ char *lp_host; /* if remote */
+ char *lp_port;
+
+ FILE *lp_lock; /* currently held lock file */
+
+ char *lp_af; /* name of accounting file */
+ long lp_br; /* if lp is a tty, set baud rate (ioctl(2) call) */
+ char *lp_cf; /* cifplot data filter */
+ char *lp_df; /* tex data filter (DVI format) */
+ char *lp_ff; /* string to send for a form feed */
+ short lp_fo; /* print a form feed when device is opened */
+ char *lp_gf; /* graph data filter (plot(3) format) */
+ short lp_hl; /* print the burst header page last */
+ short lp_ic; /* supports non-standard ioctl to indent printout */
+ char *lp_if; /* name of text filter which does accounting */
+ char *lp_lf; /* error logging file name */
+ char *lp_lo; /* name of lock file */
+ char *lp_lp; /* local printer device, or port@host for remote */
+ long lp_mc; /* maximum number of copies allowed; 0=unlimited */
+ char *lp_ms; /* if lp is a tty, a comma-separated, stty(1)-like list
+ describing the tty modes */
+ long lp_mx; /* max file size (in BUFSIZ blocks); 0=unlimited */
+ char *lp_nd; /* next directory for queues list (unimplemented) */
+ char *lp_nf; /* ditroff data filter (device independent troff) */
+ char *lp_of; /* name of output filtering program */
+ long lp_pc; /* price per foot or page in hundredths of cents */
+ long lp_pl; /* page length (in lines) */
+ long lp_pw; /* page width (in characters) */
+ long lp_px; /* page width in pixels (horizontal) */
+ long lp_py; /* page length in pixels (vertical) */
+ char *lp_rf; /* filter for printing FORTRAN style text files */
+ char *lp_rg; /* restricted group-only group members can access */
+ char *lp_rm; /* machine name for remote printer */
+ char *lp_rp; /* remote printer name argument */
+ short lp_rs; /* remote users must have a local account */
+ short lp_rw; /* open printer device for reading and writing */
+ short lp_sb; /* short banner (one line only) */
+ short lp_sc; /* suppress multiple copies */
+ char *lp_sd; /* spool directory */
+ short lp_sf; /* suppress form feeds */
+ short lp_sh; /* suppress printing of burst page header */
+ char *lp_st; /* status file name */
+ char *lp_tf; /* troff data filter (cat phototypesetter) */
+ char *lp_tr; /* trailer string to print when queue empties */
+ char *lp_vf; /* raster image filter */
+};
+
+struct lp_queue {
+ int count;
+ char **cfname;
+};
+
+struct lp_jobfilter {
+ const char *hostfrom;
+ int nuser;
+ const char *users[LP_MAXUSERS];
+ int njob;
+ int jobs[LP_MAXREQUESTS];
+};
+
+extern char *lpd_hostname;
+
+/* lp.c */
+int lp_getprinter(struct lp_printer *, const char *);
+int lp_scanprinters(struct lp_printer *);
+void lp_clearprinter(struct lp_printer *);
+int lp_readqueue(struct lp_printer *, struct lp_queue *);
+void lp_clearqueue(struct lp_queue *);
+FILE* lp_fopen(struct lp_printer *, const char *);
+int lp_stat(struct lp_printer *, const char *, struct stat *);
+int lp_unlink(struct lp_printer *, const char *);
+int lp_lock(struct lp_printer *);
+void lp_unlock(struct lp_printer *);
+int lp_getqueuestate(struct lp_printer *, int, int *);
+int lp_getcurrtask(struct lp_printer *, pid_t *, char *, size_t);
+void lp_setcurrtask(struct lp_printer *, const char *);
+int lp_getstatus(struct lp_printer *, char *, size_t);
+void lp_setstatus(struct lp_printer *, const char *, ...)
+ __attribute__((__format__ (printf, 2, 3)));
+int lp_validfilename(const char *, int);
+int lp_create(struct lp_printer *, int, size_t, const char *);
+int lp_commit(struct lp_printer *, const char *);
+
+/* lp_banner.c */
+int lp_banner(int, char *, int);
+
+/* lp_displayq.c */
+void lp_displayq(int, struct lp_printer *, int, struct lp_jobfilter *);
+
+/* lp_rmjob */
+int lp_rmjob(int, struct lp_printer *, const char *, struct lp_jobfilter *);
+
+/* lp_stty.c */
+void lp_stty(struct lp_printer *, int);
diff --git a/usr.sbin/lpd/lp_banner.c b/usr.sbin/lpd/lp_banner.c
new file mode 100644
index 00000000000..6d363df0ad4
--- /dev/null
+++ b/usr.sbin/lpd/lp_banner.c
@@ -0,0 +1,1153 @@
+/* $OpenBSD: lp_banner.c,v 1.1.1.1 2018/04/27 16:14:36 eric Exp $ */
+
+/*
+ * Adapted from the following files in src/usr.sbin/lpr/lpd:
+ *
+ * lpdchar.c,v 1.8 2016/02/28 20:55:40
+ * printjob.c,v 1.58 2016/11/22 16:03:57
+ */
+
+/*
+ * Copyright (c) 1983, 1993
+ * The Regents of the University of California. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of the University nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#include "lp.h"
+
+#define LINELEN 132
+#define BACKGND ' '
+#define HEIGHT 9
+#define DROP 3
+#define WIDTH 8
+
+/*
+ * from lpdchar.c
+ */
+
+/*
+ * Character set for line printer daemon
+ */
+
+#define c_______ 0
+#define c______1 01
+#define c_____1_ 02
+#define c____1__ 04
+#define c____11_ 06
+#define c___1___ 010
+#define c___1__1 011
+#define c___1_1_ 012
+#define c___11__ 014
+#define c__1____ 020
+#define c__1__1_ 022
+#define c__1_1__ 024
+#define c__11___ 030
+#define c__111__ 034
+#define c__111_1 035
+#define c__1111_ 036
+#define c__11111 037
+#define c_1_____ 040
+#define c_1____1 041
+#define c_1___1_ 042
+#define c_1__1__ 044
+#define c_1_1___ 050
+#define c_1_1__1 051
+#define c_1_1_1_ 052
+#define c_11____ 060
+#define c_11_11_ 066
+#define c_111___ 070
+#define c_111__1 071
+#define c_111_1_ 072
+#define c_1111__ 074
+#define c_1111_1 075
+#define c_11111_ 076
+#define c_111111 077
+#define c1______ 0100
+#define c1_____1 0101
+#define c1____1_ 0102
+#define c1____11 0103
+#define c1___1__ 0104
+#define c1___1_1 0105
+#define c1___11_ 0106
+#define c1__1___ 0110
+#define c1__1__1 0111
+#define c1__11_1 0115
+#define c1__1111 0117
+#define c1_1____ 0120
+#define c1_1___1 0121
+#define c1_1_1_1 0125
+#define c1_1_11_ 0126
+#define c1_111__ 0134
+#define c1_1111_ 0136
+#define c11____1 0141
+#define c11___1_ 0142
+#define c11___11 0143
+#define c11_1___ 0150
+#define c11_1__1 0151
+#define c111_11_ 0166
+#define c1111___ 0170
+#define c11111__ 0174
+#define c111111_ 0176
+#define c1111111 0177
+
+static const char scnkey[][HEIGHT] = /* this is relatively easy to modify */
+ /* just look: */
+{
+ { c_______,
+ c_______,
+ c_______,
+ c_______,
+ c_______,
+ c_______,
+ c_______,
+ c_______,
+ c_______ }, /* */
+
+ { c__11___,
+ c__11___,
+ c__11___,
+ c__11___,
+ c__11___,
+ c_______,
+ c_______,
+ c__11___,
+ c__11___ }, /* ! */
+
+ { c_1__1__,
+ c_1__1__,
+ c_______,
+ c_______,
+ c_______,
+ c_______,
+ c_______,
+ c_______,
+ c_______ }, /* " */
+
+ { c_______,
+ c__1_1__,
+ c__1_1__,
+ c1111111,
+ c__1_1__,
+ c1111111,
+ c__1_1__,
+ c__1_1__,
+ c_______ }, /* # */
+
+ { c___1___,
+ c_11111_,
+ c1__1__1,
+ c1__1___,
+ c_11111_,
+ c___1__1,
+ c1__1__1,
+ c_11111_,
+ c___1___ }, /* $ */
+
+ { c_1_____,
+ c1_1___1,
+ c_1___1_,
+ c____1__,
+ c___1___,
+ c__1____,
+ c_1___1_,
+ c1___1_1,
+ c_____1_ }, /* % */
+
+ { c_11____,
+ c1__1___,
+ c1___1__,
+ c_1_1___,
+ c__1____,
+ c_1_1__1,
+ c1___11_,
+ c1___11_,
+ c_111__1 }, /* & */
+
+ { c___11__,
+ c___11__,
+ c___1___,
+ c__1____,
+ c_______,
+ c_______,
+ c_______,
+ c_______,
+ c_______ }, /* ' */
+
+ { c____1__,
+ c___1___,
+ c__1____,
+ c__1____,
+ c__1____,
+ c__1____,
+ c__1____,
+ c___1___,
+ c____1__ }, /* ( */
+
+ { c__1____,
+ c___1___,
+ c____1__,
+ c____1__,
+ c____1__,
+ c____1__,
+ c____1__,
+ c___1___,
+ c__1____ }, /* ) */
+
+ { c_______,
+ c___1___,
+ c1__1__1,
+ c_1_1_1_,
+ c__111__,
+ c_1_1_1_,
+ c1__1__1,
+ c___1___,
+ c_______ }, /* * */
+
+ { c_______,
+ c___1___,
+ c___1___,
+ c___1___,
+ c1111111,
+ c___1___,
+ c___1___,
+ c___1___,
+ c_______ }, /* + */
+
+ { c_______,
+ c_______,
+ c_______,
+ c_______,
+ c__11___,
+ c__11___,
+ c__1____,
+ c_1_____,
+ c_______ }, /* , */
+
+ { c_______,
+ c_______,
+ c_______,
+ c_______,
+ c1111111,
+ c_______,
+ c_______,
+ c_______,
+ c_______ }, /* - */
+
+ { c_______,
+ c_______,
+ c_______,
+ c_______,
+ c_______,
+ c_______,
+ c_______,
+ c__11___,
+ c__11___ }, /* . */
+
+ { c_______,
+ c______1,
+ c_____1_,
+ c____1__,
+ c___1___,
+ c__1____,
+ c_1_____,
+ c1______,
+ c_______ }, /* / */
+
+ { c_11111_,
+ c1_____1,
+ c1____11,
+ c1___1_1,
+ c1__1__1,
+ c1_1___1,
+ c11____1,
+ c1_____1,
+ c_11111_ }, /* 0 */
+
+ { c___1___,
+ c__11___,
+ c_1_1___,
+ c___1___,
+ c___1___,
+ c___1___,
+ c___1___,
+ c___1___,
+ c_11111_ }, /* 1 */
+
+ { c_11111_,
+ c1_____1,
+ c______1,
+ c_____1_,
+ c__111__,
+ c_1_____,
+ c1______,
+ c1______,
+ c1111111 }, /* 2 */
+
+ { c_11111_,
+ c1_____1,
+ c______1,
+ c______1,
+ c__1111_,
+ c______1,
+ c______1,
+ c1_____1,
+ c_11111_ }, /* 3 */
+
+ { c_____1_,
+ c____11_,
+ c___1_1_,
+ c__1__1_,
+ c_1___1_,
+ c1____1_,
+ c1111111,
+ c_____1_,
+ c_____1_ }, /* 4 */
+
+ { c1111111,
+ c1______,
+ c1______,
+ c11111__,
+ c_____1_,
+ c______1,
+ c______1,
+ c1____1_,
+ c_1111__ }, /* 5 */
+
+ { c__1111_,
+ c_1_____,
+ c1______,
+ c1______,
+ c1_1111_,
+ c11____1,
+ c1_____1,
+ c1_____1,
+ c_11111_ }, /* 6 */
+
+ { c1111111,
+ c1_____1,
+ c_____1_,
+ c____1__,
+ c___1___,
+ c__1____,
+ c__1____,
+ c__1____,
+ c__1____ }, /* 7 */
+
+ { c_11111_,
+ c1_____1,
+ c1_____1,
+ c1_____1,
+ c_11111_,
+ c1_____1,
+ c1_____1,
+ c1_____1,
+ c_11111_ }, /* 8 */
+
+ { c_11111_,
+ c1_____1,
+ c1_____1,
+ c1_____1,
+ c_111111,
+ c______1,
+ c______1,
+ c1_____1,
+ c_1111__ }, /* 9 */
+
+ { c_______,
+ c_______,
+ c_______,
+ c__11___,
+ c__11___,
+ c_______,
+ c_______,
+ c__11___,
+ c__11___ }, /* : */
+
+ { c__11___,
+ c__11___,
+ c_______,
+ c_______,
+ c__11___,
+ c__11___,
+ c__1____,
+ c_1_____,
+ c_______ }, /* ; */
+
+ { c____1__,
+ c___1___,
+ c__1____,
+ c_1_____,
+ c1______,
+ c_1_____,
+ c__1____,
+ c___1___,
+ c____1__ }, /* < */
+
+ { c_______,
+ c_______,
+ c_______,
+ c1111111,
+ c_______,
+ c1111111,
+ c_______,
+ c_______,
+ c_______ }, /* = */
+
+ { c__1____,
+ c___1___,
+ c____1__,
+ c_____1_,
+ c______1,
+ c_____1_,
+ c____1__,
+ c___1___,
+ c__1____ }, /* > */
+
+ { c__1111_,
+ c_1____1,
+ c_1____1,
+ c______1,
+ c____11_,
+ c___1___,
+ c___1___,
+ c_______,
+ c___1___ }, /* ? */
+
+ { c__1111_,
+ c_1____1,
+ c1__11_1,
+ c1_1_1_1,
+ c1_1_1_1,
+ c1_1111_,
+ c1______,
+ c_1____1,
+ c__1111_ }, /* @ */
+
+ { c__111__,
+ c_1___1_,
+ c1_____1,
+ c1_____1,
+ c1111111,
+ c1_____1,
+ c1_____1,
+ c1_____1,
+ c1_____1 }, /* A */
+
+ { c111111_,
+ c_1____1,
+ c_1____1,
+ c_1____1,
+ c_11111_,
+ c_1____1,
+ c_1____1,
+ c_1____1,
+ c111111_ }, /* B */
+
+ { c__1111_,
+ c_1____1,
+ c1______,
+ c1______,
+ c1______,
+ c1______,
+ c1______,
+ c_1____1,
+ c__1111_ }, /* C */
+
+ { c11111__,
+ c_1___1_,
+ c_1____1,
+ c_1____1,
+ c_1____1,
+ c_1____1,
+ c_1____1,
+ c_1___1_,
+ c11111__ }, /* D */
+
+ { c1111111,
+ c1______,
+ c1______,
+ c1______,
+ c111111_,
+ c1______,
+ c1______,
+ c1______,
+ c1111111 }, /* E */
+
+ { c1111111,
+ c1______,
+ c1______,
+ c1______,
+ c111111_,
+ c1______,
+ c1______,
+ c1______,
+ c1______ }, /* F */
+
+ { c__1111_,
+ c_1____1,
+ c1______,
+ c1______,
+ c1______,
+ c1__1111,
+ c1_____1,
+ c_1____1,
+ c__1111_ }, /* G */
+
+ { c1_____1,
+ c1_____1,
+ c1_____1,
+ c1_____1,
+ c1111111,
+ c1_____1,
+ c1_____1,
+ c1_____1,
+ c1_____1 }, /* H */
+
+ { c_11111_,
+ c___1___,
+ c___1___,
+ c___1___,
+ c___1___,
+ c___1___,
+ c___1___,
+ c___1___,
+ c_11111_ }, /* I */
+
+ { c__11111,
+ c____1__,
+ c____1__,
+ c____1__,
+ c____1__,
+ c____1__,
+ c____1__,
+ c1___1__,
+ c_111___ }, /* J */
+
+ { c1_____1,
+ c1____1_,
+ c1___1__,
+ c1__1___,
+ c1_1____,
+ c11_1___,
+ c1___1__,
+ c1____1_,
+ c1_____1 }, /* K */
+
+ { c1______,
+ c1______,
+ c1______,
+ c1______,
+ c1______,
+ c1______,
+ c1______,
+ c1______,
+ c1111111 }, /* L */
+
+ { c1_____1,
+ c11___11,
+ c1_1_1_1,
+ c1__1__1,
+ c1_____1,
+ c1_____1,
+ c1_____1,
+ c1_____1,
+ c1_____1 }, /* M */
+
+ { c1_____1,
+ c11____1,
+ c1_1___1,
+ c1__1__1,
+ c1___1_1,
+ c1____11,
+ c1_____1,
+ c1_____1,
+ c1_____1 }, /* N */
+
+ { c__111__,
+ c_1___1_,
+ c1_____1,
+ c1_____1,
+ c1_____1,
+ c1_____1,
+ c1_____1,
+ c_1___1_,
+ c__111__ }, /* O */
+
+ { c111111_,
+ c1_____1,
+ c1_____1,
+ c1_____1,
+ c111111_,
+ c1______,
+ c1______,
+ c1______,
+ c1______ }, /* P */
+
+ { c__111__,
+ c_1___1_,
+ c1_____1,
+ c1_____1,
+ c1_____1,
+ c1__1__1,
+ c1___1_1,
+ c_1___1_,
+ c__111_1 }, /* Q */
+
+ { c111111_,
+ c1_____1,
+ c1_____1,
+ c1_____1,
+ c111111_,
+ c1__1___,
+ c1___1__,
+ c1____1_,
+ c1_____1 }, /* R */
+
+ { c_11111_,
+ c1_____1,
+ c1______,
+ c1______,
+ c_11111_,
+ c______1,
+ c______1,
+ c1_____1,
+ c_11111_ }, /* S */
+
+ { c1111111,
+ c___1___,
+ c___1___,
+ c___1___,
+ c___1___,
+ c___1___,
+ c___1___,
+ c___1___,
+ c___1___ }, /* T */
+
+ { c1_____1,
+ c1_____1,
+ c1_____1,
+ c1_____1,
+ c1_____1,
+ c1_____1,
+ c1_____1,
+ c1_____1,
+ c_11111_ }, /* U */
+
+ { c1_____1,
+ c1_____1,
+ c1_____1,
+ c_1___1_,
+ c_1___1_,
+ c__1_1__,
+ c__1_1__,
+ c___1___,
+ c___1___ }, /* V */
+
+ { c1_____1,
+ c1_____1,
+ c1_____1,
+ c1_____1,
+ c1__1__1,
+ c1__1__1,
+ c1_1_1_1,
+ c11___11,
+ c1_____1 }, /* W */
+
+ { c1_____1,
+ c1_____1,
+ c_1___1_,
+ c__1_1__,
+ c___1___,
+ c__1_1__,
+ c_1___1_,
+ c1_____1,
+ c1_____1 }, /* X */
+
+ { c1_____1,
+ c1_____1,
+ c_1___1_,
+ c__1_1__,
+ c___1___,
+ c___1___,
+ c___1___,
+ c___1___,
+ c___1___ }, /* Y */
+
+ { c1111111,
+ c______1,
+ c_____1_,
+ c____1__,
+ c___1___,
+ c__1____,
+ c_1_____,
+ c1______,
+ c1111111 }, /* Z */
+
+ { c_1111__,
+ c_1_____,
+ c_1_____,
+ c_1_____,
+ c_1_____,
+ c_1_____,
+ c_1_____,
+ c_1_____,
+ c_1111__ }, /* [ */
+
+ { c_______,
+ c1______,
+ c_1_____,
+ c__1____,
+ c___1___,
+ c____1__,
+ c_____1_,
+ c______1,
+ c_______ }, /* \ */
+
+ { c__1111_,
+ c_____1_,
+ c_____1_,
+ c_____1_,
+ c_____1_,
+ c_____1_,
+ c_____1_,
+ c_____1_,
+ c__1111_ }, /* ] */
+
+ { c___1___,
+ c__1_1__,
+ c_1___1_,
+ c1_____1,
+ c_______,
+ c_______,
+ c_______,
+ c_______ }, /* ^ */
+
+ { c_______,
+ c_______,
+ c_______,
+ c_______,
+ c_______,
+ c_______,
+ c_______,
+ c1111111,
+ c_______ }, /* _ */
+
+ { c__11___,
+ c__11___,
+ c___1___,
+ c____1__,
+ c_______,
+ c_______,
+ c_______,
+ c_______,
+ c_______ }, /* ` */
+
+ { c_______,
+ c_______,
+ c_______,
+ c_1111__,
+ c_____1_,
+ c_11111_,
+ c1_____1,
+ c1____11,
+ c_1111_1 }, /* a */
+
+ { c1______,
+ c1______,
+ c1______,
+ c1_111__,
+ c11___1_,
+ c1_____1,
+ c1_____1,
+ c11___1_,
+ c1_111__ }, /* b */
+
+ { c_______,
+ c_______,
+ c_______,
+ c_1111__,
+ c1____1_,
+ c1______,
+ c1______,
+ c1____1_,
+ c_1111__ }, /* c */
+
+ { c_____1_,
+ c_____1_,
+ c_____1_,
+ c_111_1_,
+ c1___11_,
+ c1____1_,
+ c1____1_,
+ c1___11_,
+ c_111_1_ }, /* d */
+
+ { c_______,
+ c_______,
+ c_______,
+ c_1111__,
+ c1____1_,
+ c111111_,
+ c1______,
+ c1____1_,
+ c_1111__ }, /* e */
+
+ { c___11__,
+ c__1__1_,
+ c__1____,
+ c__1____,
+ c11111__,
+ c__1____,
+ c__1____,
+ c__1____,
+ c__1____ }, /* f */
+
+ { c_111_1_,
+ c1___11_,
+ c1____1_,
+ c1____1_,
+ c1___11_,
+ c_111_1_,
+ c_____1_,
+ c1____1_,
+ c_1111__ }, /* g */
+
+ { c1______,
+ c1______,
+ c1______,
+ c1_111__,
+ c11___1_,
+ c1____1_,
+ c1____1_,
+ c1____1_,
+ c1____1_ }, /* h */
+
+ { c_______,
+ c___1___,
+ c_______,
+ c__11___,
+ c___1___,
+ c___1___,
+ c___1___,
+ c___1___,
+ c__111__ }, /* i */
+
+ { c____11_,
+ c_____1_,
+ c_____1_,
+ c_____1_,
+ c_____1_,
+ c_____1_,
+ c_____1_,
+ c_1___1_,
+ c__111__ }, /* j */
+
+ { c1______,
+ c1______,
+ c1______,
+ c1___1__,
+ c1__1___,
+ c1_1____,
+ c11_1___,
+ c1___1__,
+ c1____1_ }, /* k */
+
+ { c__11___,
+ c___1___,
+ c___1___,
+ c___1___,
+ c___1___,
+ c___1___,
+ c___1___,
+ c___1___,
+ c__111__ }, /* l */
+
+ { c_______,
+ c_______,
+ c_______,
+ c1_1_11_,
+ c11_1__1,
+ c1__1__1,
+ c1__1__1,
+ c1__1__1,
+ c1__1__1 }, /* m */
+
+ { c_______,
+ c_______,
+ c_______,
+ c1_111__,
+ c11___1_,
+ c1____1_,
+ c1____1_,
+ c1____1_,
+ c1____1_ }, /* n */
+
+ { c_______,
+ c_______,
+ c_______,
+ c_1111__,
+ c1____1_,
+ c1____1_,
+ c1____1_,
+ c1____1_,
+ c_1111__ }, /* o */
+
+ { c1_111__,
+ c11___1_,
+ c1____1_,
+ c1____1_,
+ c11___1_,
+ c1_111__,
+ c1______,
+ c1______,
+ c1______ }, /* p */
+
+ { c_111_1_,
+ c1___11_,
+ c1____1_,
+ c1____1_,
+ c1___11_,
+ c_111_1_,
+ c_____1_,
+ c_____1_,
+ c_____1_ }, /* q */
+
+ { c_______,
+ c_______,
+ c_______,
+ c1_111__,
+ c11___1_,
+ c1______,
+ c1______,
+ c1______,
+ c1______ }, /* r */
+
+ { c_______,
+ c_______,
+ c_______,
+ c_1111__,
+ c1____1_,
+ c_11____,
+ c___11__,
+ c1____1_,
+ c_1111__ }, /* s */
+
+ { c_______,
+ c__1____,
+ c__1____,
+ c11111__,
+ c__1____,
+ c__1____,
+ c__1____,
+ c__1__1_,
+ c___11__ }, /* t */
+
+ { c_______,
+ c_______,
+ c_______,
+ c1____1_,
+ c1____1_,
+ c1____1_,
+ c1____1_,
+ c1___11_,
+ c_111_1_ }, /* u */
+
+ { c_______,
+ c_______,
+ c_______,
+ c1_____1,
+ c1_____1,
+ c1_____1,
+ c_1___1_,
+ c__1_1__,
+ c___1___ }, /* v */
+
+ { c_______,
+ c_______,
+ c_______,
+ c1_____1,
+ c1__1__1,
+ c1__1__1,
+ c1__1__1,
+ c1__1__1,
+ c_11_11_ }, /* w */
+
+ { c_______,
+ c_______,
+ c_______,
+ c1____1_,
+ c_1__1__,
+ c__11___,
+ c__11___,
+ c_1__1__,
+ c1____1_ }, /* x */
+
+ { c1____1_,
+ c1____1_,
+ c1____1_,
+ c1____1_,
+ c1___11_,
+ c_111_1_,
+ c_____1_,
+ c1____1_,
+ c_1111__ }, /* y */
+
+ { c_______,
+ c_______,
+ c_______,
+ c111111_,
+ c____1__,
+ c___1___,
+ c__1____,
+ c_1_____,
+ c111111_ }, /* z */
+
+ { c___11__,
+ c__1____,
+ c__1____,
+ c__1____,
+ c_1_____,
+ c__1____,
+ c__1____,
+ c__1____,
+ c___11__ }, /* { */
+
+ { c___1___,
+ c___1___,
+ c___1___,
+ c___1___,
+ c___1___,
+ c___1___,
+ c___1___,
+ c___1___,
+ c___1___ }, /* | */
+
+ { c__11___,
+ c____1__,
+ c____1__,
+ c____1__,
+ c_____1_,
+ c____1__,
+ c____1__,
+ c____1__,
+ c__11___ }, /* } */
+
+ { c_11____,
+ c1__1__1,
+ c____11_,
+ c_______,
+ c_______,
+ c_______,
+ c_______,
+ c_______,
+ c_______ }, /* ~ */
+
+ { c_1__1__,
+ c1__1__1,
+ c__1__1_,
+ c_1__1__,
+ c1__1__1,
+ c__1__1_,
+ c_1__1__,
+ c1__1__1,
+ c__1__1_ } /* rub-out */
+};
+
+
+/*
+ * from printjob.c
+ */
+
+#include <unistd.h>
+
+static char *
+scnline(int key, char *p, int c)
+{
+ int scnwidth;
+
+ for (scnwidth = WIDTH; --scnwidth;) {
+ key <<= 1;
+ *p++ = key & 0200 ? c : BACKGND;
+ }
+ return (p);
+}
+
+#define TRC(q) (((q)-' ')&0177)
+
+static int
+dropit(int c)
+{
+ switch(c) {
+
+ case TRC('_'):
+ case TRC(';'):
+ case TRC(','):
+ case TRC('g'):
+ case TRC('j'):
+ case TRC('p'):
+ case TRC('q'):
+ case TRC('y'):
+ return (DROP);
+
+ default:
+ return (0);
+ }
+}
+
+int
+lp_banner(int scfd, char *scsp, int pw)
+{
+ char *strp;
+ int nchrs, j;
+ char outbuf[LINELEN+1], *sp, c, cc;
+ int d, scnhgt;
+
+ for (scnhgt = 0; scnhgt++ < HEIGHT+DROP; ) {
+ strp = &outbuf[0];
+ sp = scsp;
+ for (nchrs = 0; ; ) {
+ d = dropit(c = TRC(cc = *sp++));
+ if ((!d && scnhgt > HEIGHT) || (scnhgt <= DROP && d))
+ for (j = WIDTH; --j;)
+ *strp++ = BACKGND;
+ else
+ strp = scnline(scnkey[(int)c][scnhgt-1-d],
+ strp, cc);
+ if (*sp == '\0' || nchrs++ >= pw/(WIDTH+1)-1)
+ break;
+ *strp++ = BACKGND;
+ *strp++ = BACKGND;
+ }
+ while (*--strp == BACKGND && strp >= outbuf)
+ ;
+ strp++;
+ *strp++ = '\n';
+ if (write(scfd, outbuf, strp-outbuf) == -1)
+ return -1;
+ }
+
+ return 0;
+}
diff --git a/usr.sbin/lpd/lp_displayq.c b/usr.sbin/lpd/lp_displayq.c
new file mode 100644
index 00000000000..4b6472d3e1a
--- /dev/null
+++ b/usr.sbin/lpd/lp_displayq.c
@@ -0,0 +1,287 @@
+/* $OpenBSD: lp_displayq.c,v 1.1.1.1 2018/04/27 16:14:36 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 <limits.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#include "lp.h"
+
+#include "log.h"
+
+static void dolong(int, struct lp_printer *, struct lp_jobfilter *, int,
+ const char *, int);
+static void doshort(int, struct lp_printer *, struct lp_jobfilter *, int,
+ const char *, int);
+
+void
+lp_displayq(int ofd, struct lp_printer *lp, int lng, struct lp_jobfilter *jf)
+{
+ struct lp_queue q;
+ pid_t currpid;
+ char status[256], currjob[PATH_MAX];
+ int i, active, qstate;
+
+ /* Warn about the queue state if needed. */
+ if (lp_getqueuestate(lp, 0, &qstate) == -1)
+ log_warnx("cannot get queue state");
+ else {
+ if (qstate & LPQ_PRINTER_DOWN) {
+ if (lp->lp_type == PRN_LPR)
+ dprintf(ofd, "%s: ", lpd_hostname);
+ dprintf(ofd, "Warning: %s is down\n", lp->lp_name);
+ }
+ if (qstate & LPQ_QUEUE_OFF) {
+ if (lp->lp_type == PRN_LPR)
+ dprintf(ofd, "%s: ", lpd_hostname);
+ dprintf(ofd, "Warning: %s queue is turned off\n",
+ lp->lp_name);
+ }
+ }
+
+ /* Read queue content. */
+ if ((lp_readqueue(lp, &q)) == -1) {
+ log_warnx("cannot read queue");
+ if (lp->lp_type == PRN_LPR)
+ dprintf(ofd, "%s: ", lpd_hostname);
+ dprintf(ofd, "Warning: cannot read queue\n");
+ }
+
+ /* Display current printer status. */
+ if (lp_getcurrtask(lp, &currpid, currjob, sizeof(currjob)) == -1)
+ log_warnx("cannot get current task");
+
+ if (currpid) {
+ if (lp->lp_type == PRN_LPR)
+ dprintf(ofd, "%s: ", lpd_hostname);
+ if (lp_getstatus(lp, status, sizeof(status)) == -1) {
+ log_warnx("cannot read printer status");
+ dprintf(ofd, "Warning: cannot read status file\n");
+ }
+ else
+ dprintf(ofd, "%s\n", status);
+ }
+
+ /* Display queue content. */
+ if (q.count == 0) {
+ if (lp->lp_type != PRN_LPR)
+ dprintf(ofd, "no entries\n");
+ }
+ else {
+ if (currpid == 0) {
+ if (lp->lp_type == PRN_LPR)
+ dprintf(ofd, "%s: ", lpd_hostname);
+ dprintf(ofd, "Warning: no daemon present\n");
+ }
+ if (!lng) {
+ dprintf(ofd, "Rank Owner Job Files");
+ dprintf(ofd, "%43s\n", "Total Size");
+ }
+ for (i = 0; i < q.count; i++) {
+ active = !strcmp(q.cfname[i], currjob);
+ if (lng)
+ dolong(ofd, lp, jf, i+1, q.cfname[i], active);
+ else
+ doshort(ofd, lp, jf, i+1, q.cfname[i], active);
+ }
+ }
+
+ lp_clearqueue(&q);
+}
+
+static int
+checklists(const char *cfname, struct lp_jobfilter *jf, const char *person)
+{
+ int i;
+
+ if (jf->nuser == 0 && jf->njob == 0)
+ return 1;
+
+ /* Check if user is in user list. */
+ for (i = 0; i < jf->nuser; i++)
+ if (!strcmp(jf->users[i], person))
+ return 1;
+
+ /* Skip if hostnames don't match. */
+ if (strcmp(LP_JOBHOST(cfname), jf->hostfrom))
+ return 0;
+
+ /* Check for matching jobnum. */
+ for (i = 0; i < jf->njob; i++)
+ if (jf->jobs[i] == LP_JOBNUM(cfname))
+ return 1;
+
+ return 0;
+}
+
+static const char *
+rankstr(int rank, int active)
+{
+ static char buf[16];
+ const char *sfx;
+
+ if (active)
+ return "active";
+
+ sfx = "th";
+ switch (rank % 10) {
+ case 1:
+ if ((rank / 10) % 10 != 1)
+ sfx = "st";
+ break;
+ case 2:
+ sfx = "nd";
+ break;
+ case 3:
+ sfx = "rd";
+
+ }
+
+ snprintf(buf, sizeof(buf), "%d%s", rank, sfx);
+ return buf;
+}
+
+static void
+doshort(int ofd, struct lp_printer *lp, struct lp_jobfilter *jf, int rank,
+ const char *cfname, int active)
+{
+ struct stat st;
+ FILE *fp;
+ const char *fname;
+ char dfname[PATH_MAX], names[80], *line = NULL;
+ ssize_t len;
+ size_t linesz = 0, totalsz = 0;
+ int copies = 0;
+
+ fp = lp_fopen(lp, cfname);
+ if (fp == NULL) {
+ log_warn("cannot open %s", cfname);
+ return;
+ }
+
+ names[0] = '\0';
+
+ while ((len = getline(&line, &linesz, fp)) != -1) {
+ if (line[len-1] == '\n')
+ line[len - 1] = '\0';
+ switch (line[0]) {
+ case 'P':
+ if (!checklists(cfname, jf, line + 1))
+ goto end;
+
+ dprintf(ofd, "%-7s%-11s%-4i ", rankstr(rank, active),
+ line + 1, LP_JOBNUM(cfname));
+ break;
+
+ case 'N':
+ fname = line + 1;
+ if (!strcmp(fname, " "))
+ fname = "(standard input)";
+
+ if (names[0])
+ (void)strlcat(names, ", ", sizeof(names));
+ (void)strlcat(names, fname, sizeof(names));
+ if (lp_stat(lp, dfname, &st) == -1)
+ log_warn("cannot stat %s", dfname);
+ else
+ totalsz += copies * st.st_size;
+ copies = 0;
+ break;
+
+ default:
+ if (line[0] < 'a' || line[0] > 'z')
+ continue;
+ if (copies++ == 0)
+ (void)strlcpy(dfname, line+1, sizeof(dfname));
+ break;
+ }
+ }
+
+ dprintf(ofd, "%-37s %lld bytes\n", names, (long long)totalsz);
+
+ end:
+ free(line);
+}
+
+static void
+dolong(int ofd, struct lp_printer *lp, struct lp_jobfilter *jf, int rank,
+ const char *cfname, int active)
+{
+ struct stat st;
+ FILE *fp;
+ const char *fname;
+ char dfname[PATH_MAX], names[80], buf[64], *line = NULL;
+ ssize_t len;
+ size_t linesz = 0;
+ int copies = 0;
+
+ fp = lp_fopen(lp, cfname);
+ if (fp == NULL) {
+ log_warn("cannot open %s", cfname);
+ return;
+ }
+
+ names[0] = '\0';
+
+ while ((len = getline(&line, &linesz, fp)) != -1) {
+ if (line[len-1] == '\n')
+ line[len - 1] = '\0';
+ switch (line[0]) {
+ case 'P':
+ if (!checklists(cfname, jf, line + 1))
+ goto end;
+
+ snprintf(buf, sizeof(buf), "%s: %s", line+1,
+ rankstr(rank, active));
+ dprintf(ofd, "\n%-41s[job %s]\n", buf, cfname + 3);
+ break;
+
+ case 'N':
+ fname = line + 1;
+ if (!strcmp(fname, " "))
+ fname = "(standard input)";
+
+ if (copies > 1)
+ dprintf(ofd, "\t%-2d copies of %-19s", copies,
+ fname);
+ else
+ dprintf(ofd, "\t%-32s", fname);
+
+ if (lp_stat(lp, dfname, &st) == -1) {
+ log_warn("cannot stat %s", dfname);
+ dprintf(ofd, " ??? bytes\n");
+ }
+ else
+ dprintf(ofd, " %lld bytes\n",
+ (long long)st.st_size);
+ copies = 0;
+ break;
+
+ default:
+ if (line[0] < 'a' || line[0] > 'z')
+ continue;
+ if (copies++ == 0)
+ (void)strlcpy(dfname, line+1, sizeof(dfname));
+ break;
+ }
+ }
+
+ end:
+ free(line);
+}
diff --git a/usr.sbin/lpd/lp_rmjob.c b/usr.sbin/lpd/lp_rmjob.c
new file mode 100644
index 00000000000..82bb788a512
--- /dev/null
+++ b/usr.sbin/lpd/lp_rmjob.c
@@ -0,0 +1,209 @@
+/* $OpenBSD: lp_rmjob.c,v 1.1.1.1 2018/04/27 16:14:36 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 <limits.h>
+#include <signal.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#include "lp.h"
+
+#include "log.h"
+
+static int docheck(struct lp_printer *, const char *, struct lp_jobfilter *,
+ const char *, int, int);
+static void doremove(int, struct lp_printer *, const char *);
+
+int
+lp_rmjob(int ofd, struct lp_printer *lp, const char *agent,
+ struct lp_jobfilter *jf)
+{
+ struct lp_queue q;
+ char currjob[PATH_MAX];
+ pid_t currpid;
+ int active, i, killed = 0;
+
+ if ((lp_readqueue(lp, &q)) == -1) {
+ log_warnx("cannot read queue");
+ return 0;
+ }
+
+ if (q.count == 0) {
+ lp_clearqueue(&q);
+ return 0;
+ }
+
+ /*
+ * Find the current task being printed, and kill the printer process
+ * if the file is to be removed.
+ */
+ if (lp_getcurrtask(lp, &currpid, currjob, sizeof(currjob)) == -1)
+ log_warnx("cannot get current task");
+
+ if (currjob[0] && docheck(lp, agent, jf, currjob, 1, 0) == 1) {
+ if (kill(currpid, SIGINT) == -1)
+ log_warn("lpr: cannot kill printer process %d",
+ (int)currpid);
+ else
+ killed = 1;
+ }
+
+ for(i = 0; i < q.count; i++) {
+ active = !strcmp(q.cfname[i], currjob);
+ switch (docheck(lp, agent, jf, q.cfname[i], active, 0)) {
+ case 0:
+ break;
+ case 1:
+ doremove(ofd, lp, q.cfname[i]);
+ break;
+ case 2:
+ if (lp->lp_type == PRN_LPR)
+ dprintf(ofd, "%s: ", lpd_hostname);
+ dprintf(ofd, "%s: Permission denied\n", q.cfname[i]);
+ break;
+ }
+ }
+
+ lp_clearqueue(&q);
+
+ return killed;
+}
+
+/*
+ * Check if a file must be removed.
+ *
+ * Return:
+ * 0: no
+ * 1: yes
+ * 2: yes but user has no right to do so
+ */
+static int
+docheck(struct lp_printer *lp, const char *agent, struct lp_jobfilter *jf,
+ const char *cfname, int current, int local)
+{
+ FILE *fp;
+ ssize_t len;
+ size_t linesz = 0;
+ char *line = NULL, *person = NULL;
+ int i, own = 0;
+
+ /* The "-all" agent means remove all jobs from the client host. */
+ if (!strcmp(agent, "-all") && !strcmp(LP_JOBHOST(cfname), jf->hostfrom))
+ return 1;
+
+ /*
+ * Consider the root user owns local files, and files sent from
+ * the same machine.
+ */
+ if (!strcmp(agent, "root"))
+ own = local || !strcmp(LP_JOBHOST(cfname), jf->hostfrom);
+
+ /* Check if the task person matches the agent. */
+ fp = lp_fopen(lp, cfname);
+ if (fp == NULL) {
+ log_warn("cannot open %s", cfname);
+ return 0;
+ }
+ while ((len = getline(&line, &linesz, fp)) != -1) {
+ if (line[len-1] == '\n')
+ line[len - 1] = '\0';
+ if (line[0] == 'P') {
+ person = line + 1;
+ if (!strcmp(person, agent) &&
+ !strcmp(LP_JOBHOST(cfname), jf->hostfrom))
+ own = 1;
+ break;
+ }
+ }
+ fclose(fp);
+
+ if (person == NULL) {
+ free(line);
+ return 0;
+ }
+
+ /* Remove the current task if the request list is empty. */
+ if (current && jf->nuser == 0 && jf->njob == 0)
+ goto remove;
+
+ /* Check for matching jobnum. */
+ for (i = 0; i < jf->njob; i++)
+ if (jf->jobs[i] == LP_JOBNUM(cfname))
+ goto remove;
+
+ /* Check if person is in user list. */
+ for (i = 0; i < jf->nuser; i++)
+ if (!strcmp(jf->users[i], person))
+ goto remove;
+
+ free(line);
+ return 0;
+
+ remove:
+ free(line);
+ return own ? 1 : 2;
+}
+
+static void
+doremove(int ofd, struct lp_printer *lp, const char *cfname)
+{
+ FILE *fp;
+ ssize_t len;
+ size_t linesz = 0;
+ char *line = NULL;
+
+ fp = lp_fopen(lp, cfname);
+ if (fp == NULL) {
+ log_warn("cannot open %s", cfname);
+ return;
+ }
+
+ if (lp->lp_type == PRN_LPR)
+ dprintf(ofd, "%s: ", lpd_hostname);
+
+ /* First, remove the control file. */
+ if (lp_unlink(lp, cfname) == -1) {
+ log_warn("cannot unlink %s", cfname);
+ dprintf(ofd, "cannot dequeue %s\n", cfname);
+ }
+ else {
+ log_info("removed job %s", cfname);
+ dprintf(ofd, "%s dequeued\n", cfname);
+ }
+
+ /* Then unlink all data files. */
+ while ((len = getline(&line, &linesz, fp)) != -1) {
+ if (line[len-1] == '\n')
+ line[len - 1] = '\0';
+ if (line[0] != 'U')
+ continue;
+ if (strchr(line+1, '/') || strncmp(line+1, "df", 2))
+ continue;
+ if (lp->lp_type == PRN_LPR)
+ dprintf(ofd, "%s: ", lpd_hostname);
+ if (lp_unlink(lp, line + 1) == -1) {
+ log_warn("cannot unlink %s", line + 1);
+ dprintf(ofd, "cannot dequeue %s\n", line + 1);
+ }
+ else
+ dprintf(ofd, "%s dequeued\n", line + 1);
+ }
+
+ fclose(fp);
+}
diff --git a/usr.sbin/lpd/lp_stty.c b/usr.sbin/lpd/lp_stty.c
new file mode 100644
index 00000000000..80504280d59
--- /dev/null
+++ b/usr.sbin/lpd/lp_stty.c
@@ -0,0 +1,543 @@
+/* $OpenBSD: lp_stty.c,v 1.1.1.1 2018/04/27 16:14:36 eric Exp $ */
+
+/*
+ * Adapted from the following files in src/usr.sbin/lpr/lpd:
+ *
+ * extern.h,v 1.9 2015/09/29 02:37:29
+ * key.c,v 1.8 2015/01/16 06:40:18
+ * modes.c,v 1.8 2015/01/16 06:40:18
+ * printjob.c,v 1.58 2016/11/22 16:03:57
+ */
+
+static const char *printer;
+
+/*-
+ *
+ * Copyright (c) 1989, 1993
+ * The Regents of the University of California. All rights reserved.
+ * Copyright (c) 1991, 1993, 1994
+ * The Regents of the University of California. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of the University nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#include <sys/types.h>
+#include <sys/ioctl.h>
+
+#include <errno.h>
+#include <signal.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <string.h>
+#include <dirent.h>
+#include <limits.h>
+#include <termios.h>
+
+#include "lp.h"
+#include "log.h"
+
+/*
+ * from extern.h
+ */
+
+struct info {
+ int fd; /* file descriptor */
+ int ldisc; /* line discipline */
+ int off; /* turn off */
+ int set; /* need set */
+ int wset; /* need window set */
+ char *arg; /* argument */
+ struct termios t; /* terminal info */
+ struct winsize win; /* window info */
+};
+
+
+/*
+ * from key.c
+ */
+
+static int c_key(const void *, const void *);
+static void f_cbreak(struct info *);
+static void f_columns(struct info *);
+static void f_dec(struct info *);
+static void f_extproc(struct info *);
+static void f_ispeed(struct info *);
+static void f_nl(struct info *);
+static void f_ospeed(struct info *);
+static void f_raw(struct info *);
+static void f_rows(struct info *);
+static void f_sane(struct info *);
+static void f_tty(struct info *);
+
+static struct key {
+ char *name; /* name */
+ void (*f)(struct info *); /* function */
+#define F_NEEDARG 0x01 /* needs an argument */
+#define F_OFFOK 0x02 /* can turn off */
+ int flags;
+} const keys[] = {
+ { "cbreak", f_cbreak, F_OFFOK },
+ { "cols", f_columns, F_NEEDARG },
+ { "columns", f_columns, F_NEEDARG },
+ { "cooked", f_sane, 0 },
+ { "dec", f_dec, 0 },
+ { "extproc", f_extproc, F_OFFOK },
+ { "ispeed", f_ispeed, F_NEEDARG },
+ { "new", f_tty, 0 },
+ { "nl", f_nl, F_OFFOK },
+ { "old", f_tty, 0 },
+ { "ospeed", f_ospeed, F_NEEDARG },
+ { "raw", f_raw, F_OFFOK },
+ { "rows", f_rows, F_NEEDARG },
+ { "sane", f_sane, 0 },
+ { "tty", f_tty, 0 },
+};
+
+static int
+c_key(const void *a, const void *b)
+{
+
+ return (strcmp(((const struct key *)a)->name,
+ ((const struct key *)b)->name));
+}
+
+static int
+ksearch(char ***argvp, struct info *ip)
+{
+ char *name;
+ struct key *kp, tmp;
+
+ name = **argvp;
+ if (*name == '-') {
+ ip->off = 1;
+ ++name;
+ } else
+ ip->off = 0;
+
+ tmp.name = name;
+ if (!(kp = (struct key *)bsearch(&tmp, keys,
+ sizeof(keys)/sizeof(struct key), sizeof(struct key), c_key)))
+ return (0);
+ if (!(kp->flags & F_OFFOK) && ip->off) {
+ log_warnx("%s: illegal option: %s", printer, name);
+ return (1);
+ }
+ if (kp->flags & F_NEEDARG && !(ip->arg = *++*argvp)) {
+ log_warnx("%s: option requires an argument: %s", printer, name);
+ return (1);
+ }
+ kp->f(ip);
+ return (1);
+}
+
+static void
+f_cbreak(struct info *ip)
+{
+
+ if (ip->off)
+ f_sane(ip);
+ else {
+ ip->t.c_iflag |= BRKINT|IXON|IMAXBEL;
+ ip->t.c_oflag |= OPOST;
+ ip->t.c_lflag |= ISIG|IEXTEN;
+ ip->t.c_lflag &= ~ICANON;
+ ip->set = 1;
+ }
+}
+
+static void
+f_columns(struct info *ip)
+{
+
+ ip->win.ws_col = atoi(ip->arg);
+ ip->wset = 1;
+}
+
+static void
+f_dec(struct info *ip)
+{
+
+ ip->t.c_cc[VERASE] = (u_char)0177;
+ ip->t.c_cc[VKILL] = CTRL('u');
+ ip->t.c_cc[VINTR] = CTRL('c');
+ ip->t.c_lflag &= ~ECHOPRT;
+ ip->t.c_lflag |= ECHOE|ECHOKE|ECHOCTL;
+ ip->t.c_iflag &= ~IXANY;
+ ip->set = 1;
+}
+
+static void
+f_extproc(struct info *ip)
+{
+
+ if (ip->set) {
+ int tmp = 1;
+ (void)ioctl(ip->fd, TIOCEXT, &tmp);
+ } else {
+ int tmp = 0;
+ (void)ioctl(ip->fd, TIOCEXT, &tmp);
+ }
+}
+
+static void
+f_ispeed(struct info *ip)
+{
+
+ cfsetispeed(&ip->t, atoi(ip->arg));
+ ip->set = 1;
+}
+
+static void
+f_nl(struct info *ip)
+{
+
+ if (ip->off) {
+ ip->t.c_iflag |= ICRNL;
+ ip->t.c_oflag |= ONLCR;
+ } else {
+ ip->t.c_iflag &= ~ICRNL;
+ ip->t.c_oflag &= ~ONLCR;
+ }
+ ip->set = 1;
+}
+
+static void
+f_ospeed(struct info *ip)
+{
+
+ cfsetospeed(&ip->t, atoi(ip->arg));
+ ip->set = 1;
+}
+
+static void
+f_raw(struct info *ip)
+{
+
+ if (ip->off)
+ f_sane(ip);
+ else {
+ cfmakeraw(&ip->t);
+ ip->t.c_cflag &= ~(CSIZE|PARENB);
+ ip->t.c_cflag |= CS8;
+ ip->set = 1;
+ }
+}
+
+static void
+f_rows(struct info *ip)
+{
+
+ ip->win.ws_row = atoi(ip->arg);
+ ip->wset = 1;
+}
+
+static void
+f_sane(struct info *ip)
+{
+
+ ip->t.c_cflag = TTYDEF_CFLAG | (ip->t.c_cflag & (CLOCAL|CRTSCTS));
+ ip->t.c_iflag = TTYDEF_IFLAG;
+ ip->t.c_iflag |= ICRNL;
+ /* preserve user-preference flags in lflag */
+#define LKEEP (ECHOKE|ECHOE|ECHOK|ECHOPRT|ECHOCTL|ALTWERASE|TOSTOP|NOFLSH)
+ ip->t.c_lflag = TTYDEF_LFLAG | (ip->t.c_lflag & LKEEP);
+ ip->t.c_oflag = TTYDEF_OFLAG;
+ ip->set = 1;
+}
+
+static void
+f_tty(struct info *ip)
+{
+ int tmp;
+
+ tmp = TTYDISC;
+ if (ioctl(0, TIOCSETD, &tmp) == -1)
+ log_warn("%s: ioctl(TIOCSETD)", printer);
+}
+
+/*
+ * from key.c
+ */
+
+struct modes {
+ char *name;
+ long set;
+ long unset;
+};
+
+/*
+ * The code in optlist() depends on minus options following regular
+ * options, i.e. "foo" must immediately precede "-foo".
+ */
+const struct modes cmodes[] = {
+ { "cs5", CS5, CSIZE },
+ { "cs6", CS6, CSIZE },
+ { "cs7", CS7, CSIZE },
+ { "cs8", CS8, CSIZE },
+ { "cstopb", CSTOPB, 0 },
+ { "-cstopb", 0, CSTOPB },
+ { "cread", CREAD, 0 },
+ { "-cread", 0, CREAD },
+ { "parenb", PARENB, 0 },
+ { "-parenb", 0, PARENB },
+ { "parodd", PARODD, 0 },
+ { "-parodd", 0, PARODD },
+ { "parity", PARENB | CS7, PARODD | CSIZE },
+ { "-parity", CS8, PARODD | PARENB | CSIZE },
+ { "evenp", PARENB | CS7, PARODD | CSIZE },
+ { "-evenp", CS8, PARODD | PARENB | CSIZE },
+ { "oddp", PARENB | CS7 | PARODD, CSIZE },
+ { "-oddp", CS8, PARODD | PARENB | CSIZE },
+ { "pass8", CS8, PARODD | PARENB | CSIZE },
+ { "-pass8", PARENB | CS7, PARODD | CSIZE },
+ { "hupcl", HUPCL, 0 },
+ { "-hupcl", 0, HUPCL },
+ { "hup", HUPCL, 0 },
+ { "-hup", 0, HUPCL },
+ { "clocal", CLOCAL, 0 },
+ { "-clocal", 0, CLOCAL },
+ { "crtscts", CRTSCTS, 0 },
+ { "-crtscts", 0, CRTSCTS },
+ { "mdmbuf", MDMBUF, 0 },
+ { "-mdmbuf", 0, MDMBUF },
+ { NULL },
+};
+
+const struct modes imodes[] = {
+ { "ignbrk", IGNBRK, 0 },
+ { "-ignbrk", 0, IGNBRK },
+ { "brkint", BRKINT, 0 },
+ { "-brkint", 0, BRKINT },
+ { "ignpar", IGNPAR, 0 },
+ { "-ignpar", 0, IGNPAR },
+ { "parmrk", PARMRK, 0 },
+ { "-parmrk", 0, PARMRK },
+ { "inpck", INPCK, 0 },
+ { "-inpck", 0, INPCK },
+ { "istrip", ISTRIP, 0 },
+ { "-istrip", 0, ISTRIP },
+ { "inlcr", INLCR, 0 },
+ { "-inlcr", 0, INLCR },
+ { "igncr", IGNCR, 0 },
+ { "-igncr", 0, IGNCR },
+ { "icrnl", ICRNL, 0 },
+ { "-icrnl", 0, ICRNL },
+ { "iuclc", IUCLC, 0 },
+ { "-iuclc", 0, IUCLC },
+ { "ixon", IXON, 0 },
+ { "-ixon", 0, IXON },
+ { "flow", IXON, 0 },
+ { "-flow", 0, IXON },
+ { "ixoff", IXOFF, 0 },
+ { "-ixoff", 0, IXOFF },
+ { "tandem", IXOFF, 0 },
+ { "-tandem", 0, IXOFF },
+ { "ixany", IXANY, 0 },
+ { "-ixany", 0, IXANY },
+ { "decctlq", 0, IXANY },
+ { "-decctlq", IXANY, 0 },
+ { "imaxbel", IMAXBEL, 0 },
+ { "-imaxbel", 0, IMAXBEL },
+ { NULL },
+};
+
+const struct modes lmodes[] = {
+ { "echo", ECHO, 0 },
+ { "-echo", 0, ECHO },
+ { "echoe", ECHOE, 0 },
+ { "-echoe", 0, ECHOE },
+ { "crterase", ECHOE, 0 },
+ { "-crterase", 0, ECHOE },
+ { "crtbs", ECHOE, 0 }, /* crtbs not supported, close enough */
+ { "-crtbs", 0, ECHOE },
+ { "echok", ECHOK, 0 },
+ { "-echok", 0, ECHOK },
+ { "echoke", ECHOKE, 0 },
+ { "-echoke", 0, ECHOKE },
+ { "crtkill", ECHOKE, 0 },
+ { "-crtkill", 0, ECHOKE },
+ { "altwerase", ALTWERASE, 0 },
+ { "-altwerase", 0, ALTWERASE },
+ { "iexten", IEXTEN, 0 },
+ { "-iexten", 0, IEXTEN },
+ { "echonl", ECHONL, 0 },
+ { "-echonl", 0, ECHONL },
+ { "echoctl", ECHOCTL, 0 },
+ { "-echoctl", 0, ECHOCTL },
+ { "ctlecho", ECHOCTL, 0 },
+ { "-ctlecho", 0, ECHOCTL },
+ { "echoprt", ECHOPRT, 0 },
+ { "-echoprt", 0, ECHOPRT },
+ { "prterase", ECHOPRT, 0 },
+ { "-prterase", 0, ECHOPRT },
+ { "isig", ISIG, 0 },
+ { "-isig", 0, ISIG },
+ { "icanon", ICANON, 0 },
+ { "-icanon", 0, ICANON },
+ { "noflsh", NOFLSH, 0 },
+ { "-noflsh", 0, NOFLSH },
+ { "tostop", TOSTOP, 0 },
+ { "-tostop", 0, TOSTOP },
+ { "flusho", FLUSHO, 0 },
+ { "-flusho", 0, FLUSHO },
+ { "pendin", PENDIN, 0 },
+ { "-pendin", 0, PENDIN },
+ { "crt", ECHOE|ECHOKE|ECHOCTL, ECHOK|ECHOPRT },
+ { "-crt", ECHOK, ECHOE|ECHOKE|ECHOCTL },
+ { "newcrt", ECHOE|ECHOKE|ECHOCTL, ECHOK|ECHOPRT },
+ { "-newcrt", ECHOK, ECHOE|ECHOKE|ECHOCTL },
+ { "nokerninfo", NOKERNINFO, 0 },
+ { "-nokerninfo",0, NOKERNINFO },
+ { "kerninfo", 0, NOKERNINFO },
+ { "-kerninfo", NOKERNINFO, 0 },
+ { "xcase", XCASE, 0 },
+ { "-xcase", 0, XCASE },
+ { NULL },
+};
+
+const struct modes omodes[] = {
+ { "opost", OPOST, 0 },
+ { "-opost", 0, OPOST },
+ { "litout", 0, OPOST },
+ { "-litout", OPOST, 0 },
+ { "ocrnl", OCRNL, 0 },
+ { "-ocrnl", 0, OCRNL },
+ { "olcuc", OLCUC, 0 },
+ { "-olcuc", 0, OLCUC },
+ { "onlcr", ONLCR, 0 },
+ { "-onlcr", 0, ONLCR },
+ { "onlret", ONLRET, 0 },
+ { "-onlret", 0, ONLRET },
+ { "onocr", ONOCR, 0 },
+ { "-onocr", 0, ONOCR },
+ { "tabs", 0, OXTABS }, /* "preserve" tabs */
+ { "-tabs", OXTABS, 0 },
+ { "oxtabs", OXTABS, 0 },
+ { "-oxtabs", 0, OXTABS },
+ { NULL },
+};
+
+#define CHK(s) (*name == s[0] && !strcmp(name, s))
+
+static int
+msearch(char ***argvp, struct info *ip)
+{
+ const struct modes *mp;
+ char *name;
+
+ name = **argvp;
+
+ for (mp = cmodes; mp->name; ++mp)
+ if (CHK(mp->name)) {
+ ip->t.c_cflag &= ~mp->unset;
+ ip->t.c_cflag |= mp->set;
+ ip->set = 1;
+ return (1);
+ }
+ for (mp = imodes; mp->name; ++mp)
+ if (CHK(mp->name)) {
+ ip->t.c_iflag &= ~mp->unset;
+ ip->t.c_iflag |= mp->set;
+ ip->set = 1;
+ return (1);
+ }
+ for (mp = lmodes; mp->name; ++mp)
+ if (CHK(mp->name)) {
+ ip->t.c_lflag &= ~mp->unset;
+ ip->t.c_lflag |= mp->set;
+ ip->set = 1;
+ return (1);
+ }
+ for (mp = omodes; mp->name; ++mp)
+ if (CHK(mp->name)) {
+ ip->t.c_oflag &= ~mp->unset;
+ ip->t.c_oflag |= mp->set;
+ ip->set = 1;
+ return (1);
+ }
+ return (0);
+}
+
+/*
+ * from prinjob.c
+ */
+
+void
+lp_stty(struct lp_printer *lp, int fd)
+{
+ struct info i;
+ char **argv, **ap, **ep, *p, *val;
+
+ printer = lp->lp_name;
+
+ i.fd = fd;
+ i.set = i.wset = 0;
+ if (ioctl(i.fd, TIOCEXCL, (char *)0) == -1)
+ fatal("%s: ioctl(TIOCEXCL)", printer);
+
+ if (tcgetattr(i.fd, &i.t) == -1)
+ fatal("%s: tcgetattr", printer);
+
+ if (lp->lp_br > 0) {
+ cfsetspeed(&i.t, lp->lp_br);
+ i.set = 1;
+ }
+ if (lp->lp_ms) {
+ if (ioctl(i.fd, TIOCGETD, &i.ldisc) == -1)
+ fatal("%s: ioctl(TIOCGETD)", printer);
+
+ if (ioctl(i.fd, TIOCGWINSZ, &i.win) == -1)
+ log_warn("%s: ioctl(TIOCGWINSZ)", printer);
+
+ argv = calloc(256, sizeof(char *));
+ if (argv == NULL)
+ fatal("%s: malloc", printer);
+
+ p = strdup(lp->lp_ms);
+ ap = argv;
+ ep = argv + 255;
+ while ((val = strsep(&p, " \t,")) != NULL) {
+ if ((*ap++ = strdup(val)) == NULL)
+ fatal("%s: strdup", printer);
+ if (ap == ep)
+ fatal("%s: too many \"ms\" entries", printer);
+ }
+ *ap = NULL;
+
+ for (; *argv; ++argv) {
+ if (ksearch(&argv, &i))
+ continue;
+ if (msearch(&argv, &i))
+ continue;
+ log_warnx("%s: unknown stty flag: %s", printer, *argv);
+ }
+ }
+
+ if (i.set && tcsetattr(i.fd, TCSANOW, &i.t) == -1)
+ fatal("%s: tcsetattr", printer);
+
+ if (i.wset && ioctl(i.fd, TIOCSWINSZ, &i.win) == -1)
+ log_warn("%s: ioctl(TIOCSWINSZ)", printer);
+}
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);
+}
diff --git a/usr.sbin/lpd/lpd.h b/usr.sbin/lpd/lpd.h
new file mode 100644
index 00000000000..1ed1484a4b8
--- /dev/null
+++ b/usr.sbin/lpd/lpd.h
@@ -0,0 +1,148 @@
+/* $OpenBSD: lpd.h,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/queue.h>
+#include <sys/tree.h>
+#include <sys/uio.h>
+#include <sys/socket.h>
+
+#include <event.h>
+#include <imsg.h>
+#include <limits.h>
+#include <netdb.h>
+
+#define PORT_LPR 515
+
+#define LPD_CONFIG "/etc/lpd.conf"
+#define LPD_USER "daemon"
+#define LPD_SOCKET "/var/run/lpd.sock"
+
+#define LPR_DEFPRINTER "lp"
+#define LPR_MAXCMDLEN BUFSIZ
+#define LPR_MAXFILESIZE 104857600
+
+#define LOGLEVEL_CONN 2
+#define LOGLEVEL_IMSG 3
+#define LOGLEVEL_IO 4
+
+enum {
+ IMSG_NONE,
+
+ IMSG_SOCK_ENGINE,
+ IMSG_SOCK_FRONTEND,
+
+ IMSG_CONF_START,
+ IMSG_CONF_LISTENER,
+ IMSG_CONF_END,
+
+ IMSG_RES_GETADDRINFO,
+ IMSG_RES_GETADDRINFO_END,
+ IMSG_RES_GETNAMEINFO,
+
+ IMSG_LPR_ALLOWEDHOST,
+ IMSG_LPR_DISPLAYQ,
+ IMSG_LPR_PRINTJOB,
+ IMSG_LPR_RECVJOB,
+ IMSG_LPR_RECVJOB_CLEAR,
+ IMSG_LPR_RECVJOB_CF,
+ IMSG_LPR_RECVJOB_DF,
+ IMSG_LPR_RECVJOB_COMMIT,
+ IMSG_LPR_RECVJOB_ROLLBACK,
+ IMSG_LPR_RMJOB
+};
+
+enum {
+ PROC_CLIENT,
+ PROC_CONTROL,
+ PROC_ENGINE,
+ PROC_FRONTEND,
+ PROC_PRINTER,
+ PROC_PRIV
+};
+
+enum {
+ PROTO_NONE = 0,
+ PROTO_LPR
+};
+
+struct listener {
+ TAILQ_ENTRY(listener) entry;
+ int sock;
+ int proto;
+ struct sockaddr_storage ss;
+ struct timeval timeout;
+ struct event ev;
+ int pause;
+};
+
+struct lpd_conf {
+ TAILQ_HEAD(, listener) listeners;
+};
+
+struct io;
+struct imsgproc;
+
+extern struct lpd_conf *env;
+extern struct imsgproc *p_control;
+extern struct imsgproc *p_engine;
+extern struct imsgproc *p_frontend;
+extern struct imsgproc *p_priv;
+
+/* control.c */
+void control(int, int);
+
+/* engine.c */
+void engine(int, int);
+
+/* frontend.c */
+void frontend(int, int);
+void frontend_conn_closed(uint32_t);
+
+/* logmsg.c */
+const char *log_fmt_proto(int);
+const char *log_fmt_imsgtype(int);
+const char *log_fmt_proctype(int);
+const char *log_fmt_sockaddr(const struct sockaddr *);
+void log_imsg(struct imsgproc *, struct imsg *);
+void log_io(const char *, struct io *, int);
+
+/* engine_lpr.c */
+void lpr_shutdown(void);
+void lpr_dispatch_frontend(struct imsgproc *, struct imsg *);
+void lpr_printjob(const char *);
+
+/* frontend_lpr.c */
+void lpr_init(void);
+void lpr_dispatch_engine(struct imsgproc *, struct imsg *);
+void lpr_conn(uint32_t, struct listener *, int, const struct sockaddr *);
+
+/* parse.y */
+struct lpd_conf *parse_config(const char *, int);
+int cmdline_symset(char *);
+
+/* printer.c */
+void printer(int, int, const char *);
+
+/* resolver.c */
+void resolver_getaddrinfo(const char *, const char *, const struct addrinfo *,
+ void(*)(void *, int, struct addrinfo*), void *);
+void resolver_getnameinfo(const struct sockaddr *, int,
+ void(*)(void *, int, const char *, const char *), void *);
+void resolver_dispatch_request(struct imsgproc *, struct imsg *);
+void resolver_dispatch_result(struct imsgproc *, struct imsg *);
diff --git a/usr.sbin/lpd/parse.y b/usr.sbin/lpd/parse.y
new file mode 100644
index 00000000000..dca6c10f7fb
--- /dev/null
+++ b/usr.sbin/lpd/parse.y
@@ -0,0 +1,975 @@
+/* $OpenBSD: parse.y,v 1.1.1.1 2018/04/27 16:14:37 eric Exp $ */
+
+/*
+ * Copyright (c) 2008 Gilles Chehade <gilles@poolp.org>
+ * Copyright (c) 2008 Pierre-Yves Ritschard <pyr@openbsd.org>
+ * Copyright (c) 2002, 2003, 2004 Henning Brauer <henning@openbsd.org>
+ * Copyright (c) 2001 Markus Friedl. All rights reserved.
+ * Copyright (c) 2001 Daniel Hartmeier. All rights reserved.
+ * Copyright (c) 2001 Theo de Raadt. All rights reserved.
+ *
+ * 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/stat.h>
+#include <sys/ioctl.h>
+#include <sys/un.h>
+
+#include <net/if.h>
+#include <netinet/in.h>
+#include <arpa/inet.h>
+
+#include <ctype.h>
+#include <err.h>
+#include <errno.h>
+#include <ifaddrs.h>
+#include <inttypes.h>
+#include <limits.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+
+#include "lpd.h"
+
+#include "log.h"
+
+TAILQ_HEAD(files, file) files = TAILQ_HEAD_INITIALIZER(files);
+static struct file {
+ TAILQ_ENTRY(file) entry;
+ FILE *stream;
+ char *name;
+ int lineno;
+ int errors;
+} *file, *topfile;
+struct file *pushfile(const char *, int);
+int popfile(void);
+int check_file_secrecy(int, const char *);
+int yyparse(void);
+int yylex(void);
+int kw_cmp(const void *, const void *);
+int lookup(char *);
+int lgetc(int);
+int lungetc(int);
+int findeol(void);
+int yyerror(const char *, ...)
+ __attribute__((__format__ (printf, 1, 2)))
+ __attribute__((__nonnull__ (1)));
+
+TAILQ_HEAD(symhead, sym) symhead = TAILQ_HEAD_INITIALIZER(symhead);
+struct sym {
+ TAILQ_ENTRY(sym) entry;
+ int used;
+ int persist;
+ char *nam;
+ char *val;
+};
+int symset(const char *, const char *, int);
+char *symget(const char *);
+
+static int errors = 0;
+
+struct lpd_conf *conf = NULL;
+
+enum listen_options {
+ LO_FAMILY = 0x000001,
+ LO_PORT = 0x000002,
+};
+
+static struct listen_opts {
+ char *ifx;
+ int family;
+ int proto;
+ in_port_t port;
+ uint32_t options;
+} listen_opts;
+
+static void config_free(struct lpd_conf *);
+static void create_listeners(struct listen_opts *);
+static void config_listener(struct listener *, struct listen_opts *);
+static int local(struct listen_opts *);
+static int host_v4(struct listen_opts *);
+static int host_v6(struct listen_opts *);
+static int host_dns(struct listen_opts *);
+static int interface(struct listen_opts *);
+static int is_if_in_group(const char *, const char *);
+
+typedef struct {
+ union {
+ int64_t number;
+ char *string;
+ struct host *host;
+ } v;
+ int lineno;
+} YYSTYPE;
+
+%}
+
+%token ERROR ARROW INCLUDE
+%token LISTEN ON PORT INET4 INET6 LOCAL SOCKET
+%token <v.string> STRING
+%token <v.number> NUMBER
+%type <v.number> family_inet portno
+
+%%
+
+grammar : /* empty */
+ | grammar '\n'
+ | grammar include '\n'
+ | grammar varset '\n'
+ | grammar main '\n'
+ | grammar error '\n' { file->errors++; }
+ ;
+
+include : INCLUDE STRING {
+ struct file *nfile;
+
+ if ((nfile = pushfile($2, 0)) == NULL) {
+ yyerror("failed to include file %s", $2);
+ free($2);
+ YYERROR;
+ }
+ free($2);
+
+ file = nfile;
+ lungetc('\n');
+ }
+ ;
+
+varset : STRING '=' STRING {
+ char *s = $1;
+ while (*s++) {
+ if (isspace((unsigned char)*s)) {
+ yyerror("macro name cannot contain "
+ "whitespace");
+ free($1);
+ free($3);
+ YYERROR;
+ }
+ }
+ if (symset($1, $3, 0) == -1)
+ fatal("cannot store variable");
+ free($1);
+ free($3);
+ }
+ ;
+
+portno : STRING {
+ struct servent *servent;
+ servent = getservbyname($1, "tcp");
+ if (servent == NULL) {
+ yyerror("invalid port: %s", $1);
+ free($1);
+ YYERROR;
+ }
+ free($1);
+ $$ = ntohs(servent->s_port);
+ }
+ | NUMBER {
+ if ($1 <= 0 || $1 > (int)USHRT_MAX) {
+ yyerror("invalid port: %" PRId64, $1);
+ YYERROR;
+ }
+ $$ = $1;
+ }
+ ;
+
+family_inet : INET4 { $$ = AF_INET; }
+ | INET6 { $$ = AF_INET6; }
+ ;
+
+opt_listen : family_inet {
+ if (listen_opts.options & LO_FAMILY) {
+ yyerror("address family already specified");
+ YYERROR;
+ }
+ listen_opts.options |= LO_FAMILY;
+ listen_opts.family = $1;
+ }
+ | PORT portno {
+ if (listen_opts.options & LO_PORT) {
+ yyerror("port already specified");
+ YYERROR;
+ }
+ listen_opts.options |= LO_PORT;
+ listen_opts.port = htons($2);
+ }
+ ;
+
+listener : opt_listen listener
+ | /* empty */ {
+ create_listeners(&listen_opts);
+ }
+ ;
+
+main : LISTEN ON STRING {
+ memset(&listen_opts, 0, sizeof listen_opts);
+ listen_opts.ifx = $3;
+ listen_opts.family = AF_UNSPEC;
+ listen_opts.proto = PROTO_LPR;
+ listen_opts.port = htons(PORT_LPR);
+ } listener
+ ;
+%%
+
+struct keywords {
+ const char *k_name;
+ int k_val;
+};
+
+int
+yyerror(const char *fmt, ...)
+{
+ va_list ap;
+ char *msg;
+
+ file->errors++;
+ va_start(ap, fmt);
+ if (vasprintf(&msg, fmt, ap) == -1)
+ fatalx("yyerror vasprintf");
+ va_end(ap);
+ log_warnx("%s:%d: %s", file->name, yylval.lineno, msg);
+ free(msg);
+ return (0);
+}
+
+int
+kw_cmp(const void *k, const void *e)
+{
+ return (strcmp(k, ((const struct keywords *)e)->k_name));
+}
+
+int
+lookup(char *s)
+{
+ /* this has to be sorted always */
+ static const struct keywords keywords[] = {
+ { "include", INCLUDE },
+ { "inet4", INET4 },
+ { "inet6", INET6 },
+ { "listen", LISTEN },
+ { "on", ON },
+ { "port", PORT },
+ { "socket", SOCKET },
+ };
+ const struct keywords *p;
+
+ p = bsearch(s, keywords, sizeof(keywords)/sizeof(keywords[0]),
+ sizeof(keywords[0]), kw_cmp);
+
+ if (p)
+ return (p->k_val);
+ else
+ return (STRING);
+}
+
+#define MAXPUSHBACK 128
+
+unsigned char *parsebuf;
+int parseindex;
+unsigned char pushback_buffer[MAXPUSHBACK];
+int pushback_index = 0;
+
+int
+lgetc(int quotec)
+{
+ int c, next;
+
+ if (parsebuf) {
+ /* Read character from the parsebuffer instead of input. */
+ if (parseindex >= 0) {
+ c = parsebuf[parseindex++];
+ if (c != '\0')
+ return (c);
+ parsebuf = NULL;
+ } else
+ parseindex++;
+ }
+
+ if (pushback_index)
+ return (pushback_buffer[--pushback_index]);
+
+ if (quotec) {
+ if ((c = getc(file->stream)) == EOF) {
+ yyerror("reached end of file while parsing "
+ "quoted string");
+ if (file == topfile || popfile() == EOF)
+ return (EOF);
+ return (quotec);
+ }
+ return (c);
+ }
+
+ while ((c = getc(file->stream)) == '\\') {
+ next = getc(file->stream);
+ if (next != '\n') {
+ c = next;
+ break;
+ }
+ yylval.lineno = file->lineno;
+ file->lineno++;
+ }
+
+ while (c == EOF) {
+ if (file == topfile || popfile() == EOF)
+ return (EOF);
+ c = getc(file->stream);
+ }
+ return (c);
+}
+
+int
+lungetc(int c)
+{
+ if (c == EOF)
+ return (EOF);
+ if (parsebuf) {
+ parseindex--;
+ if (parseindex >= 0)
+ return (c);
+ }
+ if (pushback_index < MAXPUSHBACK-1)
+ return (pushback_buffer[pushback_index++] = c);
+ else
+ return (EOF);
+}
+
+int
+findeol(void)
+{
+ int c;
+
+ parsebuf = NULL;
+ pushback_index = 0;
+
+ /* skip to either EOF or the first real EOL */
+ while (1) {
+ c = lgetc(0);
+ if (c == '\n') {
+ file->lineno++;
+ break;
+ }
+ if (c == EOF)
+ break;
+ }
+ return (ERROR);
+}
+
+int
+yylex(void)
+{
+ unsigned char buf[8096];
+ unsigned char *p, *val;
+ int quotec, next, c;
+ int token;
+
+top:
+ p = buf;
+ while ((c = lgetc(0)) == ' ' || c == '\t')
+ ; /* nothing */
+
+ yylval.lineno = file->lineno;
+ if (c == '#')
+ while ((c = lgetc(0)) != '\n' && c != EOF)
+ ; /* nothing */
+ if (c == '$' && parsebuf == NULL) {
+ while (1) {
+ if ((c = lgetc(0)) == EOF)
+ return (0);
+
+ if (p + 1 >= buf + sizeof(buf) - 1) {
+ yyerror("string too long");
+ return (findeol());
+ }
+ if (isalnum(c) || c == '_') {
+ *p++ = c;
+ continue;
+ }
+ *p = '\0';
+ lungetc(c);
+ break;
+ }
+ val = symget(buf);
+ if (val == NULL) {
+ yyerror("macro '%s' not defined", buf);
+ return (findeol());
+ }
+ parsebuf = val;
+ parseindex = 0;
+ goto top;
+ }
+
+ switch (c) {
+ case '\'':
+ case '"':
+ quotec = c;
+ while (1) {
+ if ((c = lgetc(quotec)) == EOF)
+ return (0);
+ if (c == '\n') {
+ file->lineno++;
+ continue;
+ } else if (c == '\\') {
+ if ((next = lgetc(quotec)) == EOF)
+ return (0);
+ if (next == quotec || c == ' ' || c == '\t')
+ c = next;
+ else if (next == '\n') {
+ file->lineno++;
+ continue;
+ } else
+ lungetc(next);
+ } else if (c == quotec) {
+ *p = '\0';
+ break;
+ } else if (c == '\0') {
+ yyerror("syntax error");
+ return (findeol());
+ }
+ if (p + 1 >= buf + sizeof(buf) - 1) {
+ yyerror("string too long");
+ return (findeol());
+ }
+ *p++ = c;
+ }
+ yylval.v.string = strdup(buf);
+ if (yylval.v.string == NULL)
+ err(1, "yylex: strdup");
+ return (STRING);
+ }
+
+#define allowed_to_end_number(x) \
+ (isspace(x) || x == ')' || x ==',' || x == '/' || x == '}' || x == '=')
+
+ if (c == '-' || isdigit(c)) {
+ do {
+ *p++ = c;
+ if ((unsigned)(p-buf) >= sizeof(buf)) {
+ yyerror("string too long");
+ return (findeol());
+ }
+ } while ((c = lgetc(0)) != EOF && isdigit(c));
+ lungetc(c);
+ if (p == buf + 1 && buf[0] == '-')
+ goto nodigits;
+ if (c == EOF || allowed_to_end_number(c)) {
+ const char *errstr = NULL;
+
+ *p = '\0';
+ yylval.v.number = strtonum(buf, LLONG_MIN,
+ LLONG_MAX, &errstr);
+ if (errstr) {
+ yyerror("\"%s\" invalid number: %s",
+ buf, errstr);
+ return (findeol());
+ }
+ return (NUMBER);
+ } else {
+nodigits:
+ while (p > buf + 1)
+ lungetc(*--p);
+ c = *--p;
+ if (c == '-')
+ return (c);
+ }
+ }
+
+ if (c == '=') {
+ if ((c = lgetc(0)) != EOF && c == '>')
+ return (ARROW);
+ lungetc(c);
+ c = '=';
+ }
+
+#define allowed_in_string(x) \
+ (isalnum(x) || (ispunct(x) && x != '(' && x != ')' && \
+ x != '{' && x != '}' && x != '<' && x != '>' && \
+ x != '!' && x != '=' && x != '#' && \
+ x != ','))
+
+ if (isalnum(c) || c == ':' || c == '_') {
+ do {
+ *p++ = c;
+ if ((unsigned)(p-buf) >= sizeof(buf)) {
+ yyerror("string too long");
+ return (findeol());
+ }
+ } while ((c = lgetc(0)) != EOF && (allowed_in_string(c)));
+ lungetc(c);
+ *p = '\0';
+ if ((token = lookup(buf)) == STRING)
+ if ((yylval.v.string = strdup(buf)) == NULL)
+ err(1, "yylex: strdup");
+ return (token);
+ }
+ if (c == '\n') {
+ yylval.lineno = file->lineno;
+ file->lineno++;
+ }
+ if (c == EOF)
+ return (0);
+ return (c);
+}
+
+int
+check_file_secrecy(int fd, const char *fname)
+{
+ struct stat st;
+
+ if (fstat(fd, &st)) {
+ log_warn("warn: cannot stat %s", fname);
+ return (-1);
+ }
+ if (st.st_uid != 0 && st.st_uid != getuid()) {
+ log_warnx("warn: %s: owner not root or current user", fname);
+ return (-1);
+ }
+ if (st.st_mode & (S_IWGRP | S_IXGRP | S_IRWXO)) {
+ log_warnx("warn: %s: group/world readable/writeable", fname);
+ return (-1);
+ }
+ return (0);
+}
+
+struct file *
+pushfile(const char *name, int secret)
+{
+ struct file *nfile;
+
+ if ((nfile = calloc(1, sizeof(struct file))) == NULL) {
+ log_warn("warn: malloc");
+ return (NULL);
+ }
+ if ((nfile->name = strdup(name)) == NULL) {
+ log_warn("warn: malloc");
+ free(nfile);
+ return (NULL);
+ }
+ if ((nfile->stream = fopen(nfile->name, "r")) == NULL) {
+ log_warn("warn: %s", nfile->name);
+ free(nfile->name);
+ free(nfile);
+ return (NULL);
+ } else if (secret &&
+ check_file_secrecy(fileno(nfile->stream), nfile->name)) {
+ fclose(nfile->stream);
+ free(nfile->name);
+ free(nfile);
+ return (NULL);
+ }
+ nfile->lineno = 1;
+ TAILQ_INSERT_TAIL(&files, nfile, entry);
+ return (nfile);
+}
+
+int
+popfile(void)
+{
+ struct file *prev;
+
+ if ((prev = TAILQ_PREV(file, files, entry)) != NULL)
+ prev->errors += file->errors;
+
+ TAILQ_REMOVE(&files, file, entry);
+ fclose(file->stream);
+ free(file->name);
+ free(file);
+ file = prev;
+ return (file ? 0 : EOF);
+}
+
+struct lpd_conf *
+parse_config(const char *filename, int verbose)
+{
+ struct sym *sym, *next;
+
+ conf = calloc(1, sizeof(*conf));
+ if (conf == NULL)
+ return NULL;
+ TAILQ_INIT(&conf->listeners);
+
+ errors = 0;
+
+ if ((file = pushfile(filename, 0)) == NULL) {
+ config_free(conf);
+ return NULL;
+ }
+ topfile = file;
+
+ /*
+ * parse configuration
+ */
+ setservent(1);
+ yyparse();
+ errors = file->errors;
+ popfile();
+ endservent();
+
+ /* Free macros and check which have not been used. */
+ TAILQ_FOREACH_SAFE(sym, &symhead, entry, next) {
+ if ((verbose) && !sym->used)
+ log_warnx("warning: macro '%s' not used\n", sym->nam);
+ if (!sym->persist) {
+ free(sym->nam);
+ free(sym->val);
+ TAILQ_REMOVE(&symhead, sym, entry);
+ free(sym);
+ }
+ }
+
+ if (errors) {
+ config_free(conf);
+ return NULL;
+ }
+
+ return conf;
+}
+
+int
+symset(const char *nam, const char *val, int persist)
+{
+ struct sym *sym;
+
+ TAILQ_FOREACH(sym, &symhead, entry) {
+ if (strcmp(nam, sym->nam) == 0)
+ break;
+ }
+
+ if (sym != NULL) {
+ if (sym->persist == 1)
+ return (0);
+ else {
+ free(sym->nam);
+ free(sym->val);
+ TAILQ_REMOVE(&symhead, sym, entry);
+ free(sym);
+ }
+ }
+ if ((sym = calloc(1, sizeof(*sym))) == NULL)
+ return (-1);
+
+ sym->nam = strdup(nam);
+ if (sym->nam == NULL) {
+ free(sym);
+ return (-1);
+ }
+ sym->val = strdup(val);
+ if (sym->val == NULL) {
+ free(sym->nam);
+ free(sym);
+ return (-1);
+ }
+ sym->used = 0;
+ sym->persist = persist;
+ TAILQ_INSERT_TAIL(&symhead, sym, entry);
+ return (0);
+}
+
+int
+cmdline_symset(char *s)
+{
+ char *sym, *val;
+ int ret;
+ size_t len;
+
+ if ((val = strrchr(s, '=')) == NULL)
+ return (-1);
+
+ len = strlen(s) - strlen(val) + 1;
+ if ((sym = malloc(len)) == NULL)
+ errx(1, "cmdline_symset: malloc");
+
+ (void)strlcpy(sym, s, len);
+
+ ret = symset(sym, val + 1, 1);
+ free(sym);
+
+ return (ret);
+}
+
+char *
+symget(const char *nam)
+{
+ struct sym *sym;
+
+ TAILQ_FOREACH(sym, &symhead, entry) {
+ if (strcmp(nam, sym->nam) == 0) {
+ sym->used = 1;
+ return (sym->val);
+ }
+ }
+ return (NULL);
+}
+
+static void
+config_free(struct lpd_conf *c)
+{
+ struct listener *l;
+
+ while ((l = TAILQ_FIRST(&c->listeners))) {
+ TAILQ_REMOVE(&c->listeners, l, entry);
+ free(l);
+ }
+ free(c);
+}
+
+static void
+create_listeners(struct listen_opts *lo)
+{
+ if (local(lo))
+ return;
+ if (interface(lo))
+ return;
+ if (host_v4(lo))
+ return;
+ if (host_v6(lo))
+ return;
+ if (host_dns(lo))
+ return;
+
+ errx(1, "invalid virtual ip or interface: %s", lo->ifx);
+}
+
+static void
+config_listener(struct listener *l, struct listen_opts *lo)
+{
+ l->sock = -1;
+ l->proto = lo->proto;
+
+ TAILQ_INSERT_TAIL(&conf->listeners, l, entry);
+}
+
+static int
+local(struct listen_opts *lo)
+{
+ struct sockaddr_un *sun;
+ struct listener *h;
+
+ if (lo->family != AF_UNSPEC && lo->family != AF_LOCAL)
+ return 0;
+
+ if (lo->ifx[0] != '/')
+ return 0;
+
+ h = calloc(1, sizeof(*h));
+ sun = (struct sockaddr_un *)&h->ss;
+ sun->sun_len = sizeof(*sun);
+ sun->sun_family = AF_LOCAL;
+ if (strlcpy(sun->sun_path, lo->ifx, sizeof(sun->sun_path))
+ >= sizeof(sun->sun_path))
+ fatalx("path too long");
+
+ config_listener(h, lo);
+
+ return (1);
+}
+
+static int
+host_v4(struct listen_opts *lo)
+{
+ struct in_addr ina;
+ struct sockaddr_in *sain;
+ struct listener *h;
+
+ if (lo->family != AF_UNSPEC && lo->family != AF_INET)
+ return (0);
+
+ memset(&ina, 0, sizeof(ina));
+ if (inet_pton(AF_INET, lo->ifx, &ina) != 1)
+ return (0);
+
+ h = calloc(1, sizeof(*h));
+ sain = (struct sockaddr_in *)&h->ss;
+ sain->sin_len = sizeof(struct sockaddr_in);
+ sain->sin_family = AF_INET;
+ sain->sin_addr.s_addr = ina.s_addr;
+ sain->sin_port = lo->port;
+
+ config_listener(h, lo);
+
+ return (1);
+}
+
+static int
+host_v6(struct listen_opts *lo)
+{
+ struct in6_addr ina6;
+ struct sockaddr_in6 *sin6;
+ struct listener *h;
+
+ if (lo->family != AF_UNSPEC && lo->family != AF_INET6)
+ return (0);
+
+ memset(&ina6, 0, sizeof(ina6));
+ if (inet_pton(AF_INET6, lo->ifx, &ina6) != 1)
+ return (0);
+
+ h = calloc(1, sizeof(*h));
+ sin6 = (struct sockaddr_in6 *)&h->ss;
+ sin6->sin6_len = sizeof(struct sockaddr_in6);
+ sin6->sin6_family = AF_INET6;
+ sin6->sin6_port = lo->port;
+ memcpy(&sin6->sin6_addr, &ina6, sizeof(ina6));
+
+ config_listener(h, lo);
+
+ return (1);
+}
+
+static int
+host_dns(struct listen_opts *lo)
+{
+ struct addrinfo hints, *res0, *res;
+ int error, cnt = 0;
+ struct sockaddr_in *sain;
+ struct sockaddr_in6 *sin6;
+ struct listener *h;
+
+ memset(&hints, 0, sizeof(hints));
+ hints.ai_family = lo->family;
+ hints.ai_socktype = SOCK_STREAM;
+ hints.ai_flags = AI_ADDRCONFIG;
+ error = getaddrinfo(lo->ifx, NULL, &hints, &res0);
+ if (error == EAI_AGAIN || error == EAI_NODATA || error == EAI_NONAME)
+ return (0);
+ if (error) {
+ log_warnx("warn: host_dns: could not parse \"%s\": %s", lo->ifx,
+ gai_strerror(error));
+ return (-1);
+ }
+
+ for (res = res0; res; res = res->ai_next) {
+ if (res->ai_family != AF_INET &&
+ res->ai_family != AF_INET6)
+ continue;
+ h = calloc(1, sizeof(*h));
+ h->ss.ss_family = res->ai_family;
+ if (res->ai_family == AF_INET) {
+ sain = (struct sockaddr_in *)&h->ss;
+ sain->sin_len = sizeof(struct sockaddr_in);
+ sain->sin_addr.s_addr = ((struct sockaddr_in *)
+ res->ai_addr)->sin_addr.s_addr;
+ sain->sin_port = lo->port;
+ } else {
+ sin6 = (struct sockaddr_in6 *)&h->ss;
+ sin6->sin6_len = sizeof(struct sockaddr_in6);
+ memcpy(&sin6->sin6_addr, &((struct sockaddr_in6 *)
+ res->ai_addr)->sin6_addr, sizeof(struct in6_addr));
+ sin6->sin6_port = lo->port;
+ }
+
+ config_listener(h, lo);
+
+ cnt++;
+ }
+
+ freeaddrinfo(res0);
+ return (cnt);
+}
+
+static int
+interface(struct listen_opts *lo)
+{
+ struct ifaddrs *ifap, *p;
+ struct sockaddr_in *sain;
+ struct sockaddr_in6 *sin6;
+ struct listener *h;
+ int ret = 0;
+
+ if (getifaddrs(&ifap) == -1)
+ fatal("getifaddrs");
+
+ for (p = ifap; p != NULL; p = p->ifa_next) {
+ if (p->ifa_addr == NULL)
+ continue;
+ if (strcmp(p->ifa_name, lo->ifx) != 0 &&
+ !is_if_in_group(p->ifa_name, lo->ifx))
+ continue;
+ if (lo->family != AF_UNSPEC && lo->family != p->ifa_addr->sa_family)
+ continue;
+
+ h = calloc(1, sizeof(*h));
+
+ switch (p->ifa_addr->sa_family) {
+ case AF_INET:
+ sain = (struct sockaddr_in *)&h->ss;
+ *sain = *(struct sockaddr_in *)p->ifa_addr;
+ sain->sin_len = sizeof(struct sockaddr_in);
+ sain->sin_port = lo->port;
+ break;
+
+ case AF_INET6:
+ sin6 = (struct sockaddr_in6 *)&h->ss;
+ *sin6 = *(struct sockaddr_in6 *)p->ifa_addr;
+ sin6->sin6_len = sizeof(struct sockaddr_in6);
+ sin6->sin6_port = lo->port;
+ break;
+
+ default:
+ free(h);
+ continue;
+ }
+
+ config_listener(h, lo);
+ ret = 1;
+ }
+
+ freeifaddrs(ifap);
+
+ return ret;
+}
+
+static int
+is_if_in_group(const char *ifname, const char *groupname)
+{
+ unsigned int len;
+ struct ifgroupreq ifgr;
+ struct ifg_req *ifg;
+ int s;
+ int ret = 0;
+
+ if ((s = socket(AF_INET, SOCK_DGRAM, 0)) < 0)
+ err(1, "socket");
+
+ memset(&ifgr, 0, sizeof(ifgr));
+ if (strlcpy(ifgr.ifgr_name, ifname, IFNAMSIZ) >= IFNAMSIZ)
+ errx(1, "interface name too large");
+
+ if (ioctl(s, SIOCGIFGROUP, (caddr_t)&ifgr) == -1) {
+ if (errno == EINVAL || errno == ENOTTY)
+ goto end;
+ err(1, "SIOCGIFGROUP");
+ }
+
+ len = ifgr.ifgr_len;
+ ifgr.ifgr_groups = calloc(len/sizeof(struct ifg_req),
+ sizeof(struct ifg_req));
+ if (ioctl(s, SIOCGIFGROUP, (caddr_t)&ifgr) == -1)
+ err(1, "SIOCGIFGROUP");
+
+ ifg = ifgr.ifgr_groups;
+ for (; ifg && len >= sizeof(struct ifg_req); ifg++) {
+ len -= sizeof(struct ifg_req);
+ if (strcmp(ifg->ifgrq_group, groupname) == 0) {
+ ret = 1;
+ break;
+ }
+ }
+ free(ifgr.ifgr_groups);
+
+end:
+ close(s);
+ return ret;
+}
diff --git a/usr.sbin/lpd/printer.c b/usr.sbin/lpd/printer.c
new file mode 100644
index 00000000000..ff4b0cf3d1d
--- /dev/null
+++ b/usr.sbin/lpd/printer.c
@@ -0,0 +1,1401 @@
+/* $OpenBSD: printer.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/tree.h>
+#include <sys/wait.h>
+
+#include <errno.h>
+#include <fcntl.h>
+#include <limits.h>
+#include <pwd.h>
+#include <signal.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <string.h>
+#include <syslog.h>
+#include <unistd.h>
+#include <vis.h>
+
+#include "lpd.h"
+#include "lp.h"
+#include "log.h"
+
+#define RETRY_MAX 5
+
+#define JOB_OK 0
+#define JOB_AGAIN 1
+#define JOB_IGNORE 2
+#define JOB_ERROR 3
+
+enum {
+ OK = 0,
+ ERR_TRANSIENT, /* transient error */
+ ERR_ACCOUNT, /* account required on the local machine */
+ ERR_ACCESS, /* cannot read file */
+ ERR_INODE, /* inode changed */
+ ERR_NOIMPL, /* unimplemented feature */
+ ERR_REJECTED, /* remote server rejected a job */
+ ERR_ERROR, /* filter report an error */
+ ERR_FILTER, /* filter return invalid status */
+};
+
+struct job {
+ char *class;
+ char *host;
+ char *literal;
+ char *mail;
+ char *name;
+ char *person;
+ char *statinfo;
+ char *title;
+ int indent;
+ int pagewidth;
+};
+
+struct prnstate {
+ int pfd; /* printer fd */
+ int ofilter; /* use output filter when printing */
+ int ofd; /* output filter fd */
+ pid_t opid; /* output filter process */
+ int tof; /* true if at top of form */
+ int count; /* number of printed files */
+ char efile[64]; /* filename for filter stderr */
+};
+
+static void sighandler(int);
+static char *xstrdup(const char *);
+
+static int openfile(const char *, const char *, struct stat *, FILE **);
+static int printjob(const char *, int);
+static void printbanner(struct job *);
+static int printfile(struct job *, int, const char *, const char *);
+static int sendjob(const char *, int);
+static int sendcmd(const char *, ...);
+static int sendfile(int, const char *, const char *);
+static int recvack(void);
+static void mailreport(struct job *, int);
+
+static void prn_open(void);
+static int prn_connect(void);
+static void prn_close(void);
+static int prn_fstart(void);
+static void prn_fsuspend(void);
+static void prn_fresume(void);
+static void prn_fclose(void);
+static int prn_formfeed(void);
+static int prn_write(const char *, size_t);
+static int prn_writefile(FILE *);
+static int prn_puts(const char *);
+static ssize_t prn_read(char *, size_t);
+
+static struct lp_printer *lp;
+static struct prnstate *prn;
+
+void
+printer(int debug, int verbose, const char *name)
+{
+ struct sigaction sa;
+ struct passwd *pw;
+ struct lp_queue q;
+ int fd, jobidx, qstate, r, reload, retry;
+ char buf[64], curr[1024];
+
+ /* Early initialisation. */
+ log_init(debug, LOG_LPR);
+ log_setverbose(verbose);
+ snprintf(buf, sizeof(buf), "printer:%s", name);
+ log_procinit(buf);
+ setproctitle("%s", buf);
+
+ if ((lpd_hostname = malloc(HOST_NAME_MAX+1)) == NULL)
+ fatal("%s: malloc", __func__);
+ gethostname(lpd_hostname, HOST_NAME_MAX+1);
+
+ /* Detach from lpd session if not in debug mode. */
+ if (!debug)
+ if (setsid() == -1)
+ fatal("%s: setsid", __func__);
+
+ /* Read printer config. */
+ if ((lp = calloc(1, sizeof(*lp))) == NULL)
+ fatal("%s: calloc", __func__);
+ if (lp_getprinter(lp, name) == -1)
+ exit(1);
+
+ /*
+ * Redirect stderr if not in debug mode.
+ * This must be done before dropping priviledges.
+ */
+ if (!debug) {
+ fd = open(LP_LF(lp), O_WRONLY|O_APPEND, 0664);
+ if (fd == -1)
+ fatal("%s: open: %s", __func__, LP_LF(lp));
+ if (fd != STDERR_FILENO) {
+ if (dup2(fd, STDERR_FILENO) == -1)
+ fatalx("%s: dup2", __func__);
+ (void)close(fd);
+ }
+ }
+
+ /* Drop priviledges. */
+ if ((pw = getpwnam(LPD_USER)) == NULL)
+ fatalx("unknown user " LPD_USER);
+
+ 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");
+
+ /* Initialize the printer state. */
+ if ((prn = calloc(1, sizeof(*prn))) == NULL)
+ fatal("%s: calloc", __func__);
+ prn->pfd = -1;
+ prn->ofd = -1;
+
+ /* Setup signals */
+ memset(&sa, 0, sizeof(sa));
+ sa.sa_handler = sighandler;
+ sa.sa_flags = SA_RESTART;
+ sigemptyset(&sa.sa_mask);
+ sigaddset(&sa.sa_mask, SIGINT); /* for kill() in sighandler */
+ sigaction(SIGHUP, &sa, NULL);
+ sigaction(SIGINT, &sa, NULL);
+ sigaction(SIGQUIT, &sa, NULL);
+ sigaction(SIGTERM, &sa, NULL);
+
+ /* Grab lock file. */
+ if (lp_lock(lp) == -1) {
+ if (errno == EWOULDBLOCK) {
+ log_debug("already locked");
+ exit(0);
+ }
+ fatalx("cannot open lock file");
+ }
+
+ /* Pledge. */
+ switch (lp->lp_type) {
+ case PRN_LOCAL:
+ pledge("stdio rpath wpath cpath flock getpw tty proc exec",
+ NULL);
+ break;
+
+ case PRN_NET:
+ pledge("stdio rpath wpath cpath inet flock dns getpw proc exec",
+ NULL);
+ break;
+
+ case PRN_LPR:
+ pledge("stdio rpath wpath cpath inet flock dns getpw", NULL);
+ break;
+ }
+
+ /* Start processing the queue. */
+ memset(&q, 0, sizeof(q));
+ jobidx = 0;
+ reload = 1;
+ retry = 0;
+ curr[0] = '\0';
+
+ for (;;) {
+
+ /* Check the queue state. */
+ if (lp_getqueuestate(lp, 1, &qstate) == -1)
+ fatalx("cannot get queue state");
+ if (qstate & LPQ_PRINTER_DOWN) {
+ log_debug("printing disabled");
+ break;
+ }
+ if (qstate & LPQ_QUEUE_UPDATED) {
+ log_debug("queue updated");
+ if (reload == 0)
+ lp_clearqueue(&q);
+ reload = 1;
+ }
+
+ /* Read the queue if needed. */
+ if (reload || q.count == 0) {
+ if (lp_readqueue(lp, &q) == -1)
+ fatalx("cannot read queue");
+ jobidx = 0;
+ reload = 0;
+ }
+
+ /* If the queue is empty, all done */
+ if (q.count <= jobidx) {
+ log_debug("queue empty");
+ break;
+ }
+
+ /* Open the printer if needed. */
+ if (prn->pfd == -1) {
+ prn_open();
+ /*
+ * Opening the printer might take some time.
+ * Re-read the queue in case its state has changed.
+ */
+ lp_clearqueue(&q);
+ reload = 1;
+ continue;
+ }
+
+ if (strcmp(curr, q.cfname[jobidx]))
+ retry = 0;
+ else
+ strlcpy(curr, q.cfname[jobidx], sizeof(curr));
+
+ lp_setcurrtask(lp, q.cfname[jobidx]);
+ if (lp->lp_type == PRN_LPR)
+ r = sendjob(q.cfname[jobidx], retry);
+ else
+ r = printjob(q.cfname[jobidx], retry);
+ lp_setcurrtask(lp, NULL);
+
+ switch (r) {
+ case JOB_OK:
+ log_info("job %s %s successfully", q.cfname[jobidx],
+ (lp->lp_type == PRN_LPR) ? "relayed" : "printed");
+ break;
+ case JOB_AGAIN:
+ retry++;
+ continue;
+ case JOB_IGNORE:
+ break;
+ case JOB_ERROR:
+ log_warnx("job %s could not be printed",
+ q.cfname[jobidx]);
+ break;
+ }
+ curr[0] = '\0';
+ jobidx++;
+ retry = 0;
+ }
+
+ if (prn->pfd != -1) {
+ if (prn->count) {
+ prn_formfeed();
+ if (lp->lp_tr)
+ prn_puts(lp->lp_tr);
+ }
+ prn_close();
+ }
+
+ exit(0);
+}
+
+static void
+sighandler(int code)
+{
+ log_info("got signal %d", code);
+
+ exit(0);
+}
+
+static char *
+xstrdup(const char *s)
+{
+ char *r;
+
+ if ((r = strdup(s)) == NULL)
+ fatal("strdup");
+
+ return r;
+}
+
+/*
+ * Open control/data file, and check that the inode information is valid.
+ * On success, fill the "st" structure and set "fpp" and return 0 (OK).
+ * Return an error code on error.
+ */
+static int
+openfile(const char *fname, const char *inodeinfo, struct stat *st, FILE **fpp)
+{
+ FILE *fp;
+ char buf[64];
+
+ if (inodeinfo) {
+ log_warnx("cannot open %s: symlink not implemented", fname);
+ return ERR_NOIMPL;
+ }
+ else {
+ if ((fp = lp_fopen(lp, fname)) == NULL) {
+ log_warn("cannot open %s", fname);
+ return ERR_ACCESS;
+ }
+ }
+
+ if (fstat(fileno(fp), st) == -1) {
+ log_warn("%s: fstat: %s", __func__, fname);
+ fclose(fp);
+ return ERR_ACCESS;
+ }
+
+ if (inodeinfo) {
+ snprintf(buf, sizeof(buf), "%d %llu", st->st_dev, st->st_ino);
+ if (strcmp(inodeinfo, buf)) {
+ log_warnx("inode changed for %s", fname);
+ fclose(fp);
+ return ERR_INODE;
+ }
+ }
+
+ *fpp = fp;
+
+ return OK;
+}
+
+/*
+ * Print the job described by the control file.
+ */
+static int
+printjob(const char *cfname, int retry)
+{
+ struct job job;
+ FILE *fp;
+ ssize_t len;
+ size_t linesz = 0;
+ char *line = NULL;
+ const char *errstr;
+ long long num;
+ int r, ret = JOB_OK;
+
+ log_debug("printing job %s...", cfname);
+
+ prn->efile[0] = '\0';
+ memset(&job, 0, sizeof(job));
+ job.pagewidth = lp->lp_pw;
+
+ if ((fp = lp_fopen(lp, cfname)) == NULL) {
+ if (errno == ENOENT) {
+ log_info("missing control file %s", cfname);
+ return JOB_IGNORE;
+ }
+ /* XXX no fatal? */
+ fatal("cannot open %s", cfname);
+ }
+
+ /* First pass: setup the job structure, print banner and print data. */
+ while ((len = getline(&line, &linesz, fp)) != -1) {
+ if (line[len-1] == '\n')
+ line[len-1] = '\0';
+
+ switch (line[0]) {
+ case 'C': /* Classification */
+ if (line[1]) {
+ free(job.class);
+ job.class = xstrdup(line + 1);
+ }
+ else if (job.class == NULL)
+ job.class = xstrdup(lpd_hostname);
+ break;
+
+ case 'H': /* Host name */
+ free(job.host);
+ job.host = xstrdup(line + 1);
+ if (job.class == NULL)
+ job.class = xstrdup(line + 1);
+ break;
+
+ case 'I': /* Indent */
+ errstr = NULL;
+ num = strtonum(line + 1, 0, INT_MAX, &errstr);
+ if (errstr == NULL)
+ job.indent = num;
+ else
+ log_warnx("strtonum: %s", errstr);
+ break;
+
+ case 'J': /* Job Name */
+ free(job.name);
+ if (line[1])
+ job.name = strdup(line + 1);
+ else
+ job.name = strdup(" ");
+ break;
+
+ case 'L': /* Literal */
+ free(job.literal);
+ job.literal = xstrdup(line + 1);
+ if (!lp->lp_sh && !lp->lp_hl)
+ printbanner(&job);
+ break;
+
+ case 'M': /* Send mail to the specified user */
+ free(job.mail);
+ job.mail = xstrdup(line + 1);
+ break;
+
+ case 'N': /* Filename */
+ break;
+
+ case 'P': /* Person */
+ free(job.person);
+ job.person = xstrdup(line + 1);
+ if (lp->lp_rs && getpwnam(job.person) == NULL) {
+ mailreport(&job, ERR_ACCOUNT);
+ ret = JOB_ERROR;
+ goto remove;
+ }
+ break;
+
+ case 'S': /* Stat info for symlink protection */
+ job.statinfo = xstrdup(line + 1);
+ break;
+
+ case 'T': /* Title for pr */
+ job.title = xstrdup(line + 1);
+ break;
+
+ case 'U': /* Unlink */
+ break;
+
+ case 'W': /* Width */
+ errstr = NULL;
+ num = strtonum(line + 1, 0, INT_MAX, &errstr);
+ if (errstr == NULL)
+ job.pagewidth = num;
+ else
+ log_warnx("strtonum: %s", errstr);
+ break;
+
+ case '1': /* troff fonts */
+ case '2':
+ case '3':
+ case '4':
+ /* XXX not implemented */
+ break;
+
+ default:
+ if (line[0] < 'a' || line[0] > 'z')
+ break;
+
+ r = printfile(&job, line[0], line+1, job.statinfo);
+ free(job.statinfo);
+ job.statinfo = NULL;
+ free(job.title);
+ job.title = NULL;
+ if (r) {
+ if (r == ERR_TRANSIENT && retry < RETRY_MAX) {
+ ret = JOB_AGAIN;
+ goto done;
+ }
+ mailreport(&job, r);
+ ret = JOB_ERROR;
+ goto remove;
+ }
+ }
+ }
+
+ remove:
+ if (lp_unlink(lp, cfname) == -1)
+ log_warn("cannot unlink %s", cfname);
+
+ /* Second pass: print trailing banner, mail report, and remove files. */
+ rewind(fp);
+ while ((len = getline(&line, &linesz, fp)) != -1) {
+ if (line[len-1] == '\n')
+ line[len-1] = '\0';
+
+ switch (line[0]) {
+ case 'L': /* Literal */
+ if (ret != JOB_OK)
+ break;
+ if (!lp->lp_sh && lp->lp_hl)
+ printbanner(&job);
+ break;
+
+ case 'M': /* Send mail to the specified user */
+ if (ret == JOB_OK)
+ mailreport(&job, ret);
+ break;
+
+ case 'U': /* Unlink */
+ if (lp_unlink(lp, line + 1) == -1)
+ log_warn("cannot unlink %s", line + 1);
+ break;
+ }
+ }
+
+ done:
+ if (prn->efile[0])
+ unlink(prn->efile);
+ (void)fclose(fp);
+ free(job.class);
+ free(job.host);
+ free(job.literal);
+ free(job.mail);
+ free(job.name);
+ free(job.person);
+ free(job.statinfo);
+ free(job.title);
+ return ret;
+}
+
+static void
+printbanner(struct job *job)
+{
+ time_t t;
+
+ time(&t);
+
+ prn_formfeed();
+
+ if (lp->lp_sb) {
+ if (job->class) {
+ prn_puts(job->class);
+ prn_puts(":");
+ }
+ prn_puts(job->literal);
+ prn_puts(" Job: ");
+ prn_puts(job->name);
+ prn_puts(" Date: ");
+ prn_puts(ctime(&t));
+ prn_puts("\n");
+ } else {
+ prn_puts("\n\n\n");
+ lp_banner(prn->pfd, job->literal, lp->lp_pw);
+ prn_puts("\n\n");
+ lp_banner(prn->pfd, job->name, lp->lp_pw);
+ if (job->class) {
+ prn_puts("\n\n\n");
+ lp_banner(prn->pfd, job->class, lp->lp_pw);
+ }
+ prn_puts("\n\n\n\n\t\t\t\t\tJob: ");
+ prn_puts(job->name);
+ prn_puts("\n\t\t\t\t\tDate: ");
+ prn_puts(ctime(&t));
+ prn_puts("\n");
+ }
+
+ prn_formfeed();
+}
+
+static int
+printfile(struct job *job, int fmt, const char *fname, const char *inodeinfo)
+{
+ pid_t pid;
+ struct stat st;
+ FILE *fp;
+ size_t n;
+ int ret, argc, efd, status;
+ char *argv[16], *prog, width[16], length[16], indent[16], tmp[512];
+
+ log_debug("printing file %s...", fname);
+
+ switch (fmt) {
+ case 'f': /* print file as-is */
+ case 'o': /* print postscript file */
+ case 'l': /* print file as-is but pass control chars */
+ break;
+
+ case 'p': /* print using pr(1) */
+ case 'r': /* print fortran text file */
+ case 't': /* print troff output */
+ case 'n': /* print ditroff output */
+ case 'd': /* print tex output */
+ case 'c': /* print cifplot output */
+ case 'g': /* print plot output */
+ case 'v': /* print raster output */
+ default:
+ log_warn("unrecognized output format '%c'", fmt);
+ return ERR_NOIMPL;
+ }
+
+ if ((ret = openfile(fname, inodeinfo, &st, &fp)) != OK)
+ return ret;
+
+ prn_formfeed();
+
+ /*
+ * No input filter, just write the raw file.
+ */
+ if (!lp->lp_if) {
+ if (prn_writefile(fp) == -1)
+ ret = ERR_TRANSIENT;
+ else
+ ret = OK;
+ (void)fclose(fp);
+ return ret;
+ }
+
+ /*
+ * Otherwise, run the input filter with proper plumbing.
+ */
+
+ /* Prepare filter arguments. */
+ snprintf(width, sizeof(width), "-w%d", job->pagewidth);
+ snprintf(length, sizeof(length), "-l%ld", lp->lp_pl);
+ snprintf(indent, sizeof(indent), "-i%d", job->indent);
+ prog = strrchr(lp->lp_if, '/');
+
+ argc = 0;
+ argv[argc++] = prog ? (prog + 1) : lp->lp_if;
+ if (fmt == 'l')
+ argv[argc++] = "-c";
+ argv[argc++] = width;
+ argv[argc++] = length;
+ argv[argc++] = indent;
+ argv[argc++] = "-n";
+ argv[argc++] = job->person;
+ if (job->name) {
+ argv[argc++] = "-j";
+ argv[argc++]= job->name;
+ }
+ argv[argc++] = "-h";
+ argv[argc++] = job->host;
+ argv[argc++] = lp->lp_af;
+ argv[argc++] = NULL;
+
+ /* Open the stderr file. */
+ strlcpy(prn->efile, "/tmp/prn.XXXXXXXX", sizeof(prn->efile));
+ if ((efd = mkstemp(prn->efile)) == -1) {
+ log_warn("%s: mkstemp", __func__);
+ (void)fclose(fp);
+ return ERR_TRANSIENT;
+ }
+
+ /* Disable output filter. */
+ prn_fsuspend();
+
+ /* Run input filter */
+ switch ((pid = fork())) {
+ case -1:
+ log_warn("%s: fork", __func__);
+ close(efd);
+ prn_fresume();
+ return ERR_TRANSIENT;
+
+ case 0:
+ if (dup2(fileno(fp), STDIN_FILENO) == -1)
+ fatal("%s:, dup2", __func__);
+ if (dup2(prn->pfd, STDOUT_FILENO) == -1)
+ fatal("%s:, dup2", __func__);
+ if (dup2(efd, STDERR_FILENO) == -1)
+ fatal("%s:, dup2", __func__);
+ if (closefrom(3) == -1)
+ fatal("%s:, closefrom", __func__);
+ execv(lp->lp_if, argv);
+ log_warn("%s:, execv", __func__);
+ exit(2);
+
+ default:
+ break;
+ }
+
+ log_debug("waiting for ifilter...");
+
+ /* Wait for input filter to finish. */
+ while (waitpid(pid, &status, 0) == -1)
+ log_warn("%s: waitpid", __func__);
+
+ log_debug("ifilter done, status %d", status);
+
+ /* Resume output filter */
+ prn_fresume();
+ prn->tof = 0;
+
+ /* Copy efd to stderr */
+ if (lseek(efd, 0, SEEK_SET) == -1)
+ log_warn("%s: lseek", __func__);
+ while ((n = read(efd, tmp, sizeof(tmp))) > 0)
+ (void)write(STDERR_FILENO, tmp, n);
+ close(efd);
+
+ if (!WIFEXITED(status)) {
+ log_warn("filter terminated (termsig=%d)", WTERMSIG(status));
+ return ERR_FILTER;
+ }
+
+ switch (WEXITSTATUS(status)) {
+ case 0:
+ prn->tof = 1;
+ return OK;
+
+ case 1:
+ return ERR_TRANSIENT;
+
+ case 2:
+ return ERR_ERROR;
+
+ default:
+ log_warn("filter exited (exitstatus=%d)", WEXITSTATUS(status));
+ return ERR_FILTER;
+ }
+}
+
+static int
+sendjob(const char *cfname, int retry)
+{
+ struct job job;
+ FILE *fp;
+ ssize_t len;
+ size_t linesz = 0;
+ char *line = NULL;
+ int ret = JOB_OK, r;
+
+ log_debug("sending job %s...", cfname);
+
+ memset(&job, 0, sizeof(job));
+
+ if ((fp = lp_fopen(lp, cfname)) == NULL) {
+ if (errno == ENOENT) {
+ log_info("missing control file %s", cfname);
+ return JOB_IGNORE;
+ }
+ /* XXX no fatal? */
+ fatal("cannot open %s", cfname);
+ }
+
+ /* First pass: setup the job structure, and forward data files. */
+ while ((len = getline(&line, &linesz, fp)) != -1) {
+ if (line[len-1] == '\n')
+ line[len-1] = '\0';
+
+ switch (line[0]) {
+ case 'P':
+ free(job.person);
+ job.person = xstrdup(line + 1);
+ break;
+
+ case 'S':
+ free(job.statinfo);
+ job.statinfo = xstrdup(line + 1);
+ break;
+
+ default:
+ if (line[0] < 'a' || line[0] > 'z')
+ break;
+
+ r = sendfile('\3', line+1, job.statinfo);
+ free(job.statinfo);
+ job.statinfo = NULL;
+ if (r) {
+ if (r == ERR_TRANSIENT && retry < RETRY_MAX) {
+ ret = JOB_AGAIN;
+ goto done;
+ }
+ mailreport(&job, r);
+ ret = JOB_ERROR;
+ goto remove;
+ }
+ }
+ }
+
+ /* Send the control file. */
+ if ((r = sendfile('\2', cfname, ""))) {
+ if (r == ERR_TRANSIENT && retry < RETRY_MAX) {
+ ret = JOB_AGAIN;
+ goto done;
+ }
+ mailreport(&job, r);
+ ret = JOB_ERROR;
+ }
+
+ remove:
+ if (lp_unlink(lp, cfname) == -1)
+ log_warn("cannot unlink %s", cfname);
+
+ /* Second pass: remove files. */
+ rewind(fp);
+ while ((len = getline(&line, &linesz, fp)) != -1) {
+ if (line[len-1] == '\n')
+ line[len-1] = '\0';
+
+ switch (line[0]) {
+ case 'U':
+ if (lp_unlink(lp, line + 1) == -1)
+ log_warn("cannot unlink %s", line + 1);
+ break;
+ }
+ }
+
+ done:
+ (void)fclose(fp);
+ free(line);
+ free(job.person);
+ free(job.statinfo);
+ return ret;
+}
+
+/*
+ * Send a LPR command to the remote lpd server and return the ack.
+ * Return 0 for ack, 1 or nack, -1 and set errno on error.
+ */
+static int
+sendcmd(const char *fmt, ...)
+{
+ va_list ap;
+ unsigned char line[1024];
+ int len;
+
+ va_start(ap, fmt);
+ len = vsnprintf(line, sizeof(line), fmt, ap);
+ va_end(ap);
+
+ if (len == -1) {
+ log_warn("%s: vsnprintf", __func__);
+ return -1;
+ }
+
+ if (prn_puts(line) == -1)
+ return -1;
+
+ return recvack();
+}
+
+static int
+sendfile(int type, const char *fname, const char *inodeinfo)
+{
+ struct stat st;
+ FILE *fp = NULL;
+ int ret;
+
+ log_debug("sending file %s...", fname);
+
+ if ((ret = openfile(fname, inodeinfo, &st, &fp)) != OK)
+ return ret;
+
+ ret = ERR_TRANSIENT;
+ if (sendcmd("%c%lld %s\n", type, (long long)st.st_size, fname)) {
+ if (errno == 0)
+ ret = ERR_REJECTED;
+ goto fail;
+ }
+
+ lp_setstatus(lp, "sending %s to %s", fname, lp->lp_rm);
+ if (prn_writefile(fp) == -1 || prn_write("\0", 1) == -1)
+ goto fail;
+ if (recvack()) {
+ if (errno == 0)
+ ret = ERR_REJECTED;
+ goto fail;
+ }
+
+ ret = OK;
+
+ fail:
+ (void)fclose(fp);
+
+ if (ret == ERR_REJECTED)
+ log_warnx("%s rejected by remote host", fname);
+
+ return ret;
+}
+
+/*
+ * Read a ack response from the server.
+ * Return 0 for ack, 1 or nack, -1 and set errno on error.
+ */
+static int
+recvack(void)
+{
+ char visbuf[256 * 4 + 1];
+ unsigned char line[1024];
+ ssize_t n;
+
+ if ((n = prn_read(line, sizeof(line))) == -1)
+ return -1;
+
+ if (n == 1) {
+ errno = 0;
+ if (line[0])
+ log_warnx("%s: \\%d", lp->lp_host, line[0]);
+ return line[0] ? 1 : 0;
+ }
+
+ if (n > 256)
+ n = 256;
+ line[n] = '\0';
+ if (line[n-1] == '\n')
+ line[--n] = '\0';
+
+ strvisx(visbuf, line, n, VIS_NL | VIS_CSTYLE);
+ log_warnx("%s: %s", lp->lp_host, visbuf);
+
+ errno = 0;
+ return -1;
+}
+
+static void
+mailreport(struct job *job, int result)
+{
+ struct stat st;
+ FILE *fp = NULL, *efp;
+ const char *user;
+ char *cp;
+ int p[2], c;
+
+ if (job->mail)
+ user = job->mail;
+ else
+ user = job->person;
+ if (user == NULL) {
+ log_warnx("no user to send report to");
+ return;
+ }
+
+ if (pipe(p) == -1) {
+ log_warn("pipe");
+ return;
+ }
+
+ switch (fork()) {
+ case -1:
+ (void)close(p[0]);
+ (void)close(p[1]);
+ log_warn("fork");
+ return;
+
+ case 0:
+ if (dup2(p[0], 0) == -1)
+ fatal("%s: dup2", __func__);
+ (void)closefrom(3);
+ if ((cp = strrchr(_PATH_SENDMAIL, '/')))
+ cp++;
+ else
+ cp = _PATH_SENDMAIL;
+ execl(_PATH_SENDMAIL, cp, "-t", (char *)NULL);
+ fatal("%s: execl: %s", __func__, _PATH_SENDMAIL);
+
+ default:
+ (void)close(p[0]);
+ if ((fp = fdopen(p[1], "w")) == NULL) {
+ (void)close(p[1]);
+ log_warn("fdopen");
+ return;
+ }
+ }
+
+ fprintf(fp, "Auto-Submitted: auto-generated\n");
+ fprintf(fp, "To: %s@%s\n", user, job->host);
+ fprintf(fp, "Subject: %s printer job \"%s\"\n", lp->lp_name,
+ job->name ? job->name : "<unknown>");
+ fprintf(fp, "Reply-To: root@%s\n\n", lpd_hostname);
+ fprintf(fp, "Your printer job ");
+ if (job->name)
+ fprintf(fp, " (%s) ", job->name);
+
+ fprintf(fp, "\n");
+
+ switch (result) {
+ case OK:
+ fprintf(fp, "completed successfully");
+ break;
+
+ case ERR_ACCOUNT:
+ fprintf(fp, "could not be printed without an account on %s",
+ lpd_hostname);
+ break;
+
+ case ERR_ACCESS:
+ fprintf(fp, "could not be printed because the file could "
+ " not be read");
+ break;
+
+ case ERR_INODE:
+ fprintf(fp, "was not printed because it was not linked to"
+ " the original file");
+ break;
+
+ case ERR_NOIMPL:
+ fprintf(fp, "was not printed because some feature is missing");
+ break;
+
+ case ERR_FILTER:
+ efp = fopen(prn->efile, "r");
+ if (efp && fstat(fileno(efp), &st) == 0 && st.st_size) {
+ fprintf(fp,
+ "had the following errors and may not have printed:\n");
+ while ((c = getc(efp)) != EOF)
+ putc(c, fp);
+ }
+ else
+ fprintf(fp,
+ "had some errors and may not have printed\n");
+
+ if (efp)
+ fclose(efp);
+ break;
+
+ default:
+ printf("could not be printed");
+ break;
+ }
+
+ fprintf(fp, "\n");
+ fclose(fp);
+
+ wait(NULL);
+}
+
+static void
+prn_open(void)
+{
+ const char *status, *oldstatus;
+ int i;
+
+ switch (lp->lp_type) {
+ case PRN_LOCAL:
+ lp_setstatus(lp, "opening %s", LP_LP(lp));
+ break;
+
+ case PRN_NET:
+ case PRN_LPR:
+ lp_setstatus(lp, "connecting to %s:%s", lp->lp_host,
+ lp->lp_port ? lp->lp_port : "printer");
+ break;
+ }
+
+ status = oldstatus = NULL;
+ for (i = 0; prn->pfd == -1; i += (i < 6) ? 1 : 0) {
+
+ if (status != oldstatus) {
+ lp_setstatus(lp, "%s", status);
+ oldstatus = status;
+ }
+
+ if (i)
+ sleep(1 << i);
+
+ if ((prn->pfd = prn_connect()) == -1) {
+ status = "waiting for printer to come up";
+ continue;
+ }
+
+ if (lp->lp_type == PRN_LPR) {
+ /* Send a recvjob request. */
+ if (sendcmd("\2%s\n", LP_RP(lp))) {
+ if (errno == 0)
+ log_warnx("remote queue is disabled");
+ (void)close(prn->pfd);
+ prn->pfd = -1;
+ status = "waiting for queue to be enabled";
+ }
+ }
+ }
+
+ switch (lp->lp_type) {
+ case PRN_LOCAL:
+ lp_setstatus(lp, "printing to %s", LP_LP(lp));
+ break;
+
+ case PRN_NET:
+ lp_setstatus(lp, "printing to %s:%s", lp->lp_host, lp->lp_port);
+ break;
+
+ case PRN_LPR:
+ lp_setstatus(lp, "sending to %s", lp->lp_host);
+ break;
+ }
+
+ prn->tof = lp->lp_fo ? 0 : 1;
+ prn->count = 0;
+
+ prn_fstart();
+}
+
+/*
+ * Open the printer device, or connect to the remote host.
+ * Return the printer file desciptor, or -1 on error.
+ */
+static int
+prn_connect(void)
+{
+ struct addrinfo hints, *res, *res0;
+ int save_errno;
+ int fd, e, mode;
+ const char *cause = NULL, *host, *port;
+
+ if (lp->lp_type == PRN_LOCAL) {
+ mode = lp->lp_rw ? O_RDWR : O_WRONLY;
+ if ((fd = open(LP_LP(lp), mode)) == -1) {
+ log_warn("failed to open %s", LP_LP(lp));
+ return -1;
+ }
+
+ if (isatty(fd)) {
+ lp_stty(lp, fd);
+ return -1;
+ }
+
+ return fd;
+ }
+
+ host = lp->lp_host;
+ port = lp->lp_port ? lp->lp_port : "printer";
+
+ memset(&hints, 0, sizeof(hints));
+ hints.ai_family = AF_UNSPEC;
+ hints.ai_socktype = SOCK_STREAM;
+ if ((e = getaddrinfo(host, port, &hints, &res0))) {
+ log_warnx("%s:%s: %s", host, port, gai_strerror(e));
+ return -1;
+ }
+
+ fd = -1;
+ for (res = res0; res && fd == -1; res = res->ai_next) {
+ fd = socket(res->ai_family, res->ai_socktype, res->ai_protocol);
+ if (fd == -1)
+ cause = "socket";
+ else if (connect(fd, res->ai_addr, res->ai_addrlen) == -1) {
+ cause = "connect";
+ save_errno = errno;
+ (void)close(fd);
+ errno = save_errno;
+ fd = -1;
+ }
+ }
+
+ if (fd == -1)
+ log_warn("%s", cause);
+ else
+ log_debug("connected to %s:%s", host, port);
+
+ freeaddrinfo(res0);
+ return fd;
+}
+
+static void
+prn_close(void)
+{
+ prn_fclose();
+
+ (void)close(prn->pfd);
+ prn->pfd = -1;
+}
+
+/*
+ * Fork the output filter process if needed.
+ */
+static int
+prn_fstart(void)
+{
+ char width[32], length[32], *cp;
+ int fildes[2], i;
+
+ if (lp->lp_type == PRN_LPR || (!lp->lp_of))
+ return 0;
+
+ pipe(fildes);
+
+ for (i = 0; i < 20; i++) {
+ if (i)
+ sleep(i);
+ if ((prn->opid = fork()) != -1)
+ break;
+ log_warn("%s: fork", __func__);
+ }
+
+ if (prn->opid == -1) {
+ log_warnx("cannot fork output filter");
+ return -1;
+ }
+
+ if (prn->opid == 0) {
+ /* child */
+ dup2(fildes[0], 0);
+ dup2(prn->pfd, 1);
+ (void)closefrom(3);
+ cp = strrchr(lp->lp_of, '/');
+ if (cp)
+ cp += 1;
+ else
+ cp = lp->lp_of;
+ snprintf(width, sizeof(width), "-w%ld", lp->lp_pw);
+ snprintf(length, sizeof(length), "-l%ld", lp->lp_pl);
+ execl(lp->lp_of, cp, width, length, (char *)NULL);
+ log_warn("%s: execl", __func__);
+ exit(1);
+ }
+
+ close(fildes[0]);
+ prn->ofd = fildes[1];
+ prn->ofilter = 1;
+
+ return 0;
+}
+
+/*
+ * Suspend the output filter process.
+ */
+static void
+prn_fsuspend(void)
+{
+ pid_t pid;
+ int status;
+
+ if (prn->opid == 0)
+ return;
+
+ prn_puts("\031\1");
+ while ((pid = waitpid(WAIT_ANY, &status, WUNTRACED)) && pid != prn->opid)
+ ;
+
+ prn->ofilter = 0;
+ if (!WIFSTOPPED(status)) {
+ log_warn("output filter died (exitstatus=%d termsig=%d)",
+ WEXITSTATUS(status), WTERMSIG(status));
+ prn->opid = 0;
+ prn_fclose();
+ }
+}
+
+/*
+ * Resume the output filter process.
+ */
+static void
+prn_fresume(void)
+{
+ if (prn->opid == 0)
+ return;
+
+ if (kill(prn->opid, SIGCONT) == -1)
+ fatal("cannot restart output filter");
+ prn->ofilter = 1;
+}
+
+/*
+ * Close the output filter socket and wait for the process to terminate
+ * if currently running.
+ */
+static void
+prn_fclose(void)
+{
+ pid_t pid;
+
+ close(prn->ofd);
+ prn->ofd = -1;
+
+ while (prn->opid) {
+ pid = wait(NULL);
+ if (pid == -1)
+ log_warn("%s: wait", __func__);
+ else if (pid == prn->opid)
+ prn->opid = 0;
+ }
+}
+
+/*
+ * Write a form-feed if the printer cap requires it, and if not currently
+ * at top of form. Return 0 on success, or -1 on error and set errno.
+ */
+static int
+prn_formfeed(void)
+{
+ if (!lp->lp_sf && !prn->tof)
+ if (prn_puts(LP_FF(lp)) == -1)
+ return -1;
+ prn->tof = 1;
+ return 0;
+}
+
+/*
+ * Write data to the printer (or output filter process).
+ * Return 0 on success, or -1 and set errno.
+ */
+static int
+prn_write(const char *buf, size_t len)
+{
+ ssize_t n;
+ int fd;
+
+ fd = prn->ofilter ? prn->ofd : prn->pfd;
+
+ log_debug("prn_write(fd=%d len=%zu, of=%d pfd=%d ofd=%d)", fd, len,
+ prn->ofilter, prn->pfd, prn->ofd);
+
+ if (fd == -1) {
+ log_warnx("printer socket not opened");
+ errno = EPIPE;
+ return -1;
+ }
+
+ while (len) {
+ if ((n = write(fd, buf, len)) == -1) {
+ if (errno == EINTR)
+ continue;
+ log_warn("%s: write", __func__);
+ /* XXX close the printer */
+ return -1;
+ }
+ len -= n;
+ buf += n;
+ prn->tof = 0;
+ }
+
+ return 0;
+}
+
+/*
+ * Write a string to the printer (or output filter process).
+ * Return 0 on success, or -1 and set errno.
+ */
+static int
+prn_puts(const char *buf)
+{
+ return prn_write(buf, strlen(buf));
+}
+
+/*
+ * Write the FILE content to the printer (or output filter process).
+ * Return 0 on success, or -1 and set errno.
+ */
+static int
+prn_writefile(FILE *fp)
+{
+ char buf[BUFSIZ];
+ size_t r;
+
+ while (!feof(fp)) {
+ r = fread(buf, 1, sizeof(buf), fp);
+ if (ferror(fp)) {
+ log_warn("%s: fread", __func__);
+ return -1;
+ }
+ if (r && (prn_write(buf, r) == -1))
+ return -1;
+ }
+
+ return 0;
+}
+
+/*
+ * Read data from the printer socket into the given buffer.
+ * Return 0 on success, or -1 and set errno.
+ */
+static ssize_t
+prn_read(char *buf, size_t sz)
+{
+ ssize_t n;
+
+ for (;;) {
+ if ((n = read(prn->pfd, buf, sz)) == 0) {
+ errno = ECONNRESET;
+ n = -1;
+ }
+ if (n == -1) {
+ if (errno == EINTR)
+ continue;
+ /* XXX close printer? */
+ log_warn("%s: read", __func__);
+ return -1;
+ }
+ return n;
+ }
+}
diff --git a/usr.sbin/lpd/proc.c b/usr.sbin/lpd/proc.c
new file mode 100644
index 00000000000..d2eba4fb27c
--- /dev/null
+++ b/usr.sbin/lpd/proc.c
@@ -0,0 +1,508 @@
+/* $OpenBSD: proc.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/queue.h>
+#include <sys/socket.h>
+
+#include <errno.h>
+#include <event.h>
+#include <imsg.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#include "log.h"
+#include "proc.h"
+
+struct imsgproc {
+ TAILQ_ENTRY(imsgproc) tqe;
+ int type;
+ int instance;
+ char *title;
+ pid_t pid;
+ void *arg;
+ void (*cb)(struct imsgproc *, struct imsg *, void *);
+ struct imsgbuf imsgbuf;
+ short events;
+ struct event ev;
+
+ struct {
+ const uint8_t *pos;
+ const uint8_t *end;
+ } m_in;
+
+ struct m_out {
+ char *buf;
+ size_t alloc;
+ size_t pos;
+ uint32_t type;
+ uint32_t peerid;
+ pid_t pid;
+ int fd;
+ } m_out;
+};
+
+static struct imsgproc *proc_new(int);
+static void proc_setsock(struct imsgproc *, int);
+static void proc_callback(struct imsgproc *, struct imsg *);
+static void proc_dispatch(int, short, void *);
+static void proc_event_add(struct imsgproc *);
+
+static TAILQ_HEAD(, imsgproc) procs = TAILQ_HEAD_INITIALIZER(procs);
+
+pid_t
+proc_getpid(struct imsgproc *p)
+{
+ return p->pid;
+}
+
+int
+proc_gettype(struct imsgproc *p)
+{
+ return p->type;
+}
+
+int
+proc_getinstance(struct imsgproc *p)
+{
+ return p->instance;
+}
+
+const char *
+proc_gettitle(struct imsgproc *p)
+{
+ return p->title;
+}
+
+struct imsgproc *
+proc_bypid(pid_t pid)
+{
+ struct imsgproc *p;
+
+ TAILQ_FOREACH(p, &procs, tqe)
+ if (pid == p->pid)
+ return p;
+
+ return NULL;
+}
+
+struct imsgproc *
+proc_exec(int type, char **argv)
+{
+ struct imsgproc *p;
+ int sp[2];
+ pid_t pid;
+
+ p = proc_new(type);
+ if (p == NULL) {
+ log_warn("%s: proc_new", __func__);
+ return NULL;
+ }
+
+ if (socketpair(AF_UNIX, SOCK_STREAM|SOCK_NONBLOCK, PF_UNSPEC, sp) == -1) {
+ log_warn("%s: socketpair", __func__);
+ proc_free(p);
+ return NULL;
+ }
+
+ switch (pid = fork()) {
+ case -1:
+ log_warn("%s: fork", __func__);
+ close(sp[0]);
+ close(sp[1]);
+ proc_free(p);
+ return NULL;
+ case 0:
+ break;
+ default:
+ close(sp[0]);
+ p->pid = pid;
+ proc_setsock(p, sp[1]);
+ return p;
+ }
+
+ if (dup2(sp[0], 3) == -1)
+ fatal("%s: dup2", __func__);
+
+ if (closefrom(4) == -1)
+ fatal("%s: closefrom", __func__);
+
+ execvp(argv[0], argv);
+ fatal("%s: execvp: %s", __func__, argv[0]);
+}
+
+struct imsgproc *
+proc_attach(int type, int fd)
+{
+ struct imsgproc *p;
+
+ p = proc_new(type);
+ if (p == NULL)
+ return NULL;
+
+ proc_setsock(p, fd);
+ return p;
+}
+
+void
+proc_settitle(struct imsgproc *p, const char *title)
+{
+ free(p->title);
+ if (title) {
+ p->title = strdup(title);
+ if (p->title == NULL)
+ log_warn("%s: strdup", __func__);
+ }
+ else
+ p->title = NULL;
+}
+
+void
+proc_setpid(struct imsgproc *p, pid_t pid)
+{
+ p->pid = pid;
+}
+
+void
+proc_setcallback(struct imsgproc *p,
+ void(*cb)(struct imsgproc *, struct imsg *, void *), void *arg)
+{
+ p->cb = cb;
+ p->arg = arg;
+}
+
+void
+proc_enable(struct imsgproc *p)
+{
+ proc_event_add(p);
+}
+
+void
+proc_free(struct imsgproc *p)
+{
+ if (p == NULL)
+ return;
+
+ TAILQ_REMOVE(&procs, p, tqe);
+
+ if (event_initialized(&p->ev))
+ event_del(&p->ev);
+ close(p->imsgbuf.fd);
+ imsg_clear(&p->imsgbuf);
+ free(p->title);
+ free(p);
+}
+
+static struct imsgproc *
+proc_new(int type)
+{
+ struct imsgproc *p;
+
+ p = calloc(1, sizeof(*p));
+ if (p == NULL)
+ return NULL;
+
+ p->type = type;
+ p->instance = -1;
+ p->pid = -1;
+ imsg_init(&p->imsgbuf, -1);
+
+ TAILQ_INSERT_TAIL(&procs, p, tqe);
+
+ return p;
+}
+
+static void
+proc_setsock(struct imsgproc *p, int sock)
+{
+ p->imsgbuf.fd = sock;
+ p->imsgbuf.w.fd = sock;
+}
+
+static void
+proc_event_add(struct imsgproc *p)
+{
+ short events;
+
+ events = EV_READ;
+ if (p->imsgbuf.w.queued)
+ events |= EV_WRITE;
+
+ if (p->events)
+ event_del(&p->ev);
+
+ p->events = events;
+ if (events) {
+ event_set(&p->ev, p->imsgbuf.fd, events, proc_dispatch, p);
+ event_add(&p->ev, NULL);
+ }
+}
+
+static void
+proc_callback(struct imsgproc *p, struct imsg *imsg)
+{
+ if (imsg != NULL) {
+ p->m_in.pos = imsg->data;
+ p->m_in.end = p->m_in.pos + (imsg->hdr.len - sizeof(imsg->hdr));
+ }
+ else {
+ p->m_in.pos = NULL;
+ p->m_in.end = NULL;
+ }
+
+ p->cb(p, imsg, p->arg);
+}
+
+static void
+proc_dispatch(int fd, short event, void *arg)
+{
+ struct imsgproc *p = arg;
+ struct imsg imsg;
+ ssize_t n;
+
+ p->events = 0;
+
+ if (event & EV_READ) {
+ n = imsg_read(&p->imsgbuf);
+ switch (n) {
+ case -1:
+ if (errno == EAGAIN)
+ break;
+ log_warn("%s: imsg_read", __func__);
+ proc_callback(p, NULL);
+ return;
+ case 0:
+ /* This pipe is dead. */
+ proc_callback(p, NULL);
+ return;
+ default:
+ break;
+ }
+ }
+
+ if (event & EV_WRITE) {
+ n = msgbuf_write(&p->imsgbuf.w);
+ switch (n) {
+ case -1:
+ if (errno == EAGAIN)
+ break;
+ log_warn("%s: msgbuf_write", __func__);
+ proc_callback(p, NULL);
+ return;
+ case 0:
+ /* This pipe is dead. */
+ proc_callback(p, NULL);
+ return;
+ default:
+ break;
+ }
+ }
+
+ for (;;) {
+ if ((n = imsg_get(&p->imsgbuf, &imsg)) == -1) {
+ log_warn("%s: imsg_get", __func__);
+ proc_callback(p, NULL);
+ return;
+ }
+ if (n == 0)
+ break;
+
+ proc_callback(p, &imsg);
+ imsg_free(&imsg);
+ }
+
+ proc_event_add(p);
+}
+
+void
+m_compose(struct imsgproc *p, uint32_t type, uint32_t peerid, pid_t pid, int fd,
+ const void *data, size_t len)
+{
+ if (imsg_compose(&p->imsgbuf, type, peerid, pid, fd, data, len) == -1)
+ fatal("%s: imsg_compose", __func__);
+
+ proc_event_add(p);
+}
+
+void
+m_create(struct imsgproc *p, uint32_t type, uint32_t peerid, pid_t pid, int fd)
+{
+ p->m_out.pos = 0;
+ p->m_out.type = type;
+ p->m_out.peerid = peerid;
+ p->m_out.pid = pid;
+ p->m_out.fd = fd;
+}
+
+void
+m_close(struct imsgproc *p)
+{
+ if (imsg_compose(&p->imsgbuf, p->m_out.type, p->m_out.peerid,
+ p->m_out.pid, p->m_out.fd, p->m_out.buf, p->m_out.pos) == -1)
+ fatal("%s: imsg_compose", __func__);
+
+ proc_event_add(p);
+}
+
+void
+m_add(struct imsgproc *p, const void *data, size_t len)
+{
+ size_t alloc;
+ void *tmp;
+
+ if (p->m_out.pos + len + IMSG_HEADER_SIZE > MAX_IMSGSIZE)
+ fatalx("%s: message too large", __func__);
+
+ alloc = p->m_out.alloc ? p->m_out.alloc : 128;
+ while (p->m_out.pos + len > alloc)
+ alloc *= 2;
+ if (alloc != p->m_out.alloc) {
+ tmp = recallocarray(p->m_out.buf, p->m_out.alloc, alloc, 1);
+ if (tmp == NULL)
+ fatal("%s: reallocarray", __func__);
+ p->m_out.alloc = alloc;
+ p->m_out.buf = tmp;
+ }
+
+ memmove(p->m_out.buf + p->m_out.pos, data, len);
+ p->m_out.pos += len;
+}
+
+void
+m_add_int(struct imsgproc *p, int v)
+{
+ m_add(p, &v, sizeof(v));
+};
+
+void
+m_add_u32(struct imsgproc *p, uint32_t v)
+{
+ m_add(p, &v, sizeof(v));
+};
+
+void
+m_add_u64(struct imsgproc *p, uint64_t v)
+{
+ m_add(p, &v, sizeof(v));
+}
+
+void
+m_add_size(struct imsgproc *p, size_t v)
+{
+ m_add(p, &v, sizeof(v));
+}
+
+void
+m_add_time(struct imsgproc *p, time_t v)
+{
+ m_add(p, &v, sizeof(v));
+}
+
+void
+m_add_string(struct imsgproc *p, const char *str)
+{
+ m_add(p, str, strlen(str) + 1);
+}
+
+void
+m_add_sockaddr(struct imsgproc *p, const struct sockaddr *sa)
+{
+ m_add_size(p, sa->sa_len);
+ m_add(p, sa, sa->sa_len);
+}
+
+void
+m_end(struct imsgproc *p)
+{
+ if (p->m_in.pos != p->m_in.end)
+ fatal("%s: %zi bytes left", __func__,
+ p->m_in.end - p->m_in.pos);
+}
+
+int
+m_is_eom(struct imsgproc *p)
+{
+ return (p->m_in.pos == p->m_in.end);
+}
+
+void
+m_get(struct imsgproc *p, void *dst, size_t sz)
+{
+ if (sz > MAX_IMSGSIZE ||
+ p->m_in.end - p->m_in.pos < (ssize_t)sz )
+ fatalx("%s: %zu bytes requested, %zi left", __func__, sz,
+ p->m_in.end - p->m_in.pos);
+
+ memmove(dst, p->m_in.pos, sz);
+ p->m_in.pos += sz;
+}
+
+void
+m_get_int(struct imsgproc *p, int *dst)
+{
+ m_get(p, dst, sizeof(*dst));
+}
+
+void
+m_get_u32(struct imsgproc *p, uint32_t *dst)
+{
+ m_get(p, dst, sizeof(*dst));
+}
+
+void
+m_get_u64(struct imsgproc *p, uint64_t *dst)
+{
+ m_get(p, dst, sizeof(*dst));
+}
+
+void
+m_get_size(struct imsgproc *p, size_t *dst)
+{
+ m_get(p, dst, sizeof(*dst));
+}
+
+void
+m_get_time(struct imsgproc *p, time_t *dst)
+{
+ m_get(p, dst, sizeof(*dst));
+}
+
+void
+m_get_string(struct imsgproc *p, const char **dst)
+{
+ char *end;
+
+ if (p->m_in.pos >= p->m_in.end)
+ fatalx("%s: no data left", __func__);
+
+ end = memchr(p->m_in.pos, 0, p->m_in.end - p->m_in.pos);
+ if (end == NULL)
+ fatalx("%s: unterminated string", __func__);
+
+ *dst = p->m_in.pos;
+ p->m_in.pos = end + 1;
+}
+
+void
+m_get_sockaddr(struct imsgproc *p, struct sockaddr *dst)
+{
+ size_t len;
+
+ m_get_size(p, &len);
+ m_get(p, dst, len);
+}
diff --git a/usr.sbin/lpd/proc.h b/usr.sbin/lpd/proc.h
new file mode 100644
index 00000000000..12648c5d427
--- /dev/null
+++ b/usr.sbin/lpd/proc.h
@@ -0,0 +1,57 @@
+/* $OpenBSD: proc.h,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.
+ */
+
+struct imsgproc;
+
+struct imsgproc *proc_bypid(pid_t);
+struct imsgproc *proc_exec(int, char **);
+struct imsgproc *proc_attach(int, int);
+void proc_enable(struct imsgproc *);
+void proc_free(struct imsgproc *);
+pid_t proc_getpid(struct imsgproc *);
+int proc_gettype(struct imsgproc *);
+int proc_getinstance(struct imsgproc *);
+const char *proc_gettitle(struct imsgproc *);
+void proc_setpid(struct imsgproc *, pid_t);
+void proc_settitle(struct imsgproc *, const char *);
+void proc_setinstance(struct imsgproc *, int);
+void proc_setcallback(struct imsgproc *,
+ void(*)(struct imsgproc *, struct imsg *, void *), void *);
+
+void m_compose(struct imsgproc *, uint32_t, uint32_t, pid_t, int, const void *,
+ size_t);
+void m_create(struct imsgproc *, uint32_t, uint32_t, pid_t, int);
+void m_close(struct imsgproc *);
+void m_add(struct imsgproc *, const void *, size_t);
+void m_add_int(struct imsgproc *, int);
+void m_add_u32(struct imsgproc *, uint32_t);
+void m_add_u64(struct imsgproc *, uint64_t);
+void m_add_size(struct imsgproc *, size_t);
+void m_add_time(struct imsgproc *, time_t);
+void m_add_string(struct imsgproc *, const char *);
+void m_add_sockaddr(struct imsgproc *, const struct sockaddr *);
+void m_end(struct imsgproc *);
+int m_is_eom(struct imsgproc *);
+void m_get(struct imsgproc *, void *, size_t);
+void m_get_int(struct imsgproc *, int *);
+void m_get_u32(struct imsgproc *, uint32_t *);
+void m_get_u64(struct imsgproc *, uint64_t *);
+void m_get_size(struct imsgproc *, size_t *);
+void m_get_time(struct imsgproc *, time_t *);
+void m_get_string(struct imsgproc *, const char **);
+void m_get_sockaddr(struct imsgproc *, struct sockaddr *);
diff --git a/usr.sbin/lpd/resolver.c b/usr.sbin/lpd/resolver.c
new file mode 100644
index 00000000000..8725b06d848
--- /dev/null
+++ b/usr.sbin/lpd/resolver.c
@@ -0,0 +1,355 @@
+/* $OpenBSD: resolver.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/socket.h>
+#include <netinet/in.h>
+
+#include <asr.h>
+#include <ctype.h>
+#include <errno.h>
+#include <limits.h>
+#include <stdarg.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#include "lpd.h"
+
+#include "log.h"
+#include "proc.h"
+
+struct request {
+ SPLAY_ENTRY(request) entry;
+ uint32_t id;
+ void (*cb_ai)(void *, int, struct addrinfo *);
+ void (*cb_ni)(void *, int, const char *, const char *);
+ void *arg;
+ struct addrinfo *ai;
+};
+
+struct session {
+ uint32_t reqid;
+ struct imsgproc *proc;
+ char *host;
+ char *serv;
+};
+
+SPLAY_HEAD(reqtree, request);
+
+static void resolver_init(void);
+static void resolver_getaddrinfo_cb(struct asr_result *, void *);
+static void resolver_getnameinfo_cb(struct asr_result *, void *);
+
+static int request_cmp(struct request *, struct request *);
+SPLAY_PROTOTYPE(reqtree, request, entry, request_cmp);
+
+static struct reqtree reqs;
+
+void
+resolver_getaddrinfo(const char *hostname, const char *servname,
+ const struct addrinfo *hints, void (*cb)(void *, int, struct addrinfo *),
+ void *arg)
+{
+ struct request *req;
+
+ resolver_init();
+
+ req = calloc(1, sizeof(*req));
+ if (req == NULL) {
+ cb(arg, EAI_MEMORY, NULL);
+ return;
+ }
+
+ while (req->id == 0 || SPLAY_FIND(reqtree, &reqs, req))
+ req->id = arc4random();
+ req->cb_ai = cb;
+ req->arg = arg;
+
+ SPLAY_INSERT(reqtree, &reqs, req);
+
+ m_create(p_engine, IMSG_RES_GETADDRINFO, req->id, 0, -1);
+ m_add_int(p_engine, hints ? hints->ai_flags : 0);
+ m_add_int(p_engine, hints ? hints->ai_family : 0);
+ m_add_int(p_engine, hints ? hints->ai_socktype : 0);
+ m_add_int(p_engine, hints ? hints->ai_protocol : 0);
+ m_add_string(p_engine, hostname);
+ m_add_string(p_engine, servname ? servname : "");
+ m_close(p_engine);
+}
+
+void
+resolver_getnameinfo(const struct sockaddr *sa, int flags,
+ void(*cb)(void *, int, const char *, const char *), void *arg)
+{
+ struct request *req;
+
+ resolver_init();
+
+ req = calloc(1, sizeof(*req));
+ if (req == NULL) {
+ cb(arg, EAI_MEMORY, NULL, NULL);
+ return;
+ }
+
+ while (req->id == 0 || SPLAY_FIND(reqtree, &reqs, req))
+ req->id = arc4random();
+ req->cb_ni = cb;
+ req->arg = arg;
+
+ m_create(p_engine, IMSG_RES_GETNAMEINFO, req->id, 0, -1);
+ m_add_sockaddr(p_engine, sa);
+ m_add_int(p_engine, flags);
+ m_close(p_engine);
+}
+
+void
+resolver_dispatch_request(struct imsgproc *proc, struct imsg *imsg)
+{
+ const char *hostname, *servname;
+ struct session *s;
+ struct asr_query *q;
+ struct addrinfo hints;
+ struct sockaddr_storage ss;
+ struct sockaddr *sa;
+ uint32_t reqid;
+ int flags, save_errno;
+
+ reqid = imsg->hdr.peerid;
+
+ switch (imsg->hdr.type) {
+
+ case IMSG_RES_GETADDRINFO:
+ servname = NULL;
+ memset(&hints, 0 , sizeof(hints));
+ m_get_int(proc, &hints.ai_flags);
+ m_get_int(proc, &hints.ai_family);
+ m_get_int(proc, &hints.ai_socktype);
+ m_get_int(proc, &hints.ai_protocol);
+ m_get_string(proc, &hostname);
+ if (!m_is_eom(proc))
+ m_get_string(proc, &servname);
+ m_end(proc);
+
+ s = NULL;
+ q = NULL;
+ if ((s = calloc(1, sizeof(*s))) &&
+ (q = getaddrinfo_async(hostname, servname, &hints, NULL)) &&
+ (event_asr_run(q, resolver_getaddrinfo_cb, s))) {
+ s->reqid = reqid;
+ s->proc = proc;
+ break;
+ }
+ save_errno = errno;
+
+ if (q)
+ asr_abort(q);
+ if (s)
+ free(s);
+
+ m_create(proc, IMSG_RES_GETADDRINFO_END, reqid, 0, -1);
+ m_add_int(proc, EAI_SYSTEM);
+ m_add_int(proc, save_errno);
+ m_close(proc);
+ break;
+
+ case IMSG_RES_GETNAMEINFO:
+ sa = (struct sockaddr*)&ss;
+ m_get_sockaddr(proc, sa);
+ m_get_int(proc, &flags);
+ m_end(proc);
+
+ s = NULL;
+ q = NULL;
+ if ((s = calloc(1, sizeof(*s))) &&
+ (s->host = malloc(NI_MAXHOST)) &&
+ (s->serv = malloc(NI_MAXSERV)) &&
+ (q = getnameinfo_async(sa, sa->sa_len, s->host, NI_MAXHOST,
+ s->serv, NI_MAXSERV, flags, NULL)) &&
+ (event_asr_run(q, resolver_getnameinfo_cb, s))) {
+ s->reqid = reqid;
+ s->proc = proc;
+ break;
+ }
+ save_errno = errno;
+
+ if (q)
+ asr_abort(q);
+ if (s) {
+ free(s->host);
+ free(s->serv);
+ free(s);
+ }
+
+ m_create(proc, IMSG_RES_GETNAMEINFO, reqid, 0, -1);
+ m_add_int(proc, EAI_SYSTEM);
+ m_add_int(proc, save_errno);
+ m_add_string(proc, "");
+ m_add_string(proc, "");
+ m_close(proc);
+ break;
+
+ default:
+ fatalx("%s: %s", __func__, log_fmt_imsgtype(imsg->hdr.type));
+ }
+}
+
+void
+resolver_dispatch_result(struct imsgproc *proc, struct imsg *imsg)
+{
+ struct request key, *req;
+ struct sockaddr_storage ss;
+ struct addrinfo *ai;
+ const char *cname, *host, *serv;
+ int gai_errno;
+
+ key.id = imsg->hdr.peerid;
+ req = SPLAY_FIND(reqtree, &reqs, &key);
+ if (req == NULL)
+ fatalx("%s: unknown request %08x", __func__, imsg->hdr.peerid);
+
+ switch (imsg->hdr.type) {
+
+ case IMSG_RES_GETADDRINFO:
+ ai = calloc(1, sizeof(*ai));
+ if (ai == NULL) {
+ log_warn("%s: calloc", __func__);
+ break;
+ }
+ m_get_int(proc, &ai->ai_flags);
+ m_get_int(proc, &ai->ai_family);
+ m_get_int(proc, &ai->ai_socktype);
+ m_get_int(proc, &ai->ai_protocol);
+ m_get_sockaddr(proc, (struct sockaddr *)&ss);
+ m_get_string(proc, &cname);
+ m_end(proc);
+
+ ai->ai_addr = malloc(ss.ss_len);
+ if (ai->ai_addr == NULL) {
+ log_warn("%s: malloc", __func__);
+ free(ai);
+ break;
+ }
+
+ memmove(ai->ai_addr, &ss, ss.ss_len);
+
+ if (cname[0]) {
+ ai->ai_canonname = strdup(cname);
+ if (ai->ai_canonname == NULL) {
+ log_warn("%s: strdup", __func__);
+ free(ai->ai_addr);
+ free(ai);
+ break;
+ }
+ }
+
+ ai->ai_next = req->ai;
+ req->ai = ai;
+ break;
+
+ case IMSG_RES_GETADDRINFO_END:
+ m_get_int(proc, &gai_errno);
+ m_get_int(proc, &errno);
+ m_end(proc);
+
+ SPLAY_REMOVE(reqtree, &reqs, req);
+ req->cb_ai(req->arg, gai_errno, req->ai);
+ free(req);
+ break;
+
+ case IMSG_RES_GETNAMEINFO:
+ m_get_int(proc, &gai_errno);
+ m_get_int(proc, &errno);
+ m_get_string(proc, &host);
+ m_get_string(proc, &serv);
+ m_end(proc);
+
+ SPLAY_REMOVE(reqtree, &reqs, req);
+ req->cb_ni(req->arg, gai_errno, host[0] ? host : NULL,
+ serv[0] ? serv : NULL);
+ free(req);
+ break;
+ }
+}
+
+static void
+resolver_init(void)
+{
+ static int init = 0;
+
+ if (init == 0) {
+ SPLAY_INIT(&reqs);
+ init = 1;
+ }
+}
+
+static void
+resolver_getaddrinfo_cb(struct asr_result *ar, void *arg)
+{
+ struct session *s = arg;
+ struct addrinfo *ai;
+
+ for (ai = ar->ar_addrinfo; ai; ai = ai->ai_next) {
+ m_create(s->proc, IMSG_RES_GETADDRINFO, s->reqid, 0, -1);
+ m_add_int(s->proc, ai->ai_flags);
+ m_add_int(s->proc, ai->ai_family);
+ m_add_int(s->proc, ai->ai_socktype);
+ m_add_int(s->proc, ai->ai_protocol);
+ m_add_sockaddr(s->proc, ai->ai_addr);
+ m_add_string(s->proc, ai->ai_canonname ?
+ ai->ai_canonname : "");
+ m_close(s->proc);
+ }
+
+ m_create(s->proc, IMSG_RES_GETADDRINFO_END, s->reqid, 0, -1);
+ m_add_int(s->proc, ar->ar_gai_errno);
+ m_add_int(s->proc, ar->ar_errno);
+ m_close(s->proc);
+
+ freeaddrinfo(ar->ar_addrinfo);
+ free(s);
+}
+
+static void
+resolver_getnameinfo_cb(struct asr_result *ar, void *arg)
+{
+ struct session *s = arg;
+
+ m_create(s->proc, IMSG_RES_GETNAMEINFO, s->reqid, 0, -1);
+ m_add_int(s->proc, ar->ar_gai_errno);
+ m_add_int(s->proc, ar->ar_errno);
+ m_add_string(s->proc, s->host ? s->host : "");
+ m_add_string(s->proc, s->serv ? s->serv : "");
+ m_close(s->proc);
+
+ free(s->host);
+ free(s->serv);
+ free(s);
+}
+
+static int
+request_cmp(struct request *a, struct request *b)
+{
+ if (a->id < b->id)
+ return (-1);
+ if (a->id > b->id)
+ return (1);
+ return (0);
+}
+
+SPLAY_GENERATE(reqtree, request, entry, request_cmp);