diff options
author | Eric Faurot <eric@cvs.openbsd.org> | 2018-04-27 16:14:38 +0000 |
---|---|---|
committer | Eric Faurot <eric@cvs.openbsd.org> | 2018-04-27 16:14:38 +0000 |
commit | 396409d3f48b2b81d5bdeeec18d62fc6f8b48238 (patch) | |
tree | 74790a98b23741a16fd61a15f1c88ddb1b1f2e0b /usr.sbin/lpd | |
parent | eb452b015e8c2a6e5ce80d6872e2adec6c181392 (diff) |
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/Makefile | 34 | ||||
-rw-r--r-- | usr.sbin/lpd/control.c | 251 | ||||
-rw-r--r-- | usr.sbin/lpd/engine.c | 173 | ||||
-rw-r--r-- | usr.sbin/lpd/engine_lpr.c | 748 | ||||
-rw-r--r-- | usr.sbin/lpd/frontend.c | 335 | ||||
-rw-r--r-- | usr.sbin/lpd/frontend_lpr.c | 805 | ||||
-rw-r--r-- | usr.sbin/lpd/io.c | 1149 | ||||
-rw-r--r-- | usr.sbin/lpd/io.h | 85 | ||||
-rw-r--r-- | usr.sbin/lpd/iobuf.c | 467 | ||||
-rw-r--r-- | usr.sbin/lpd/iobuf.h | 68 | ||||
-rw-r--r-- | usr.sbin/lpd/log.c | 199 | ||||
-rw-r--r-- | usr.sbin/lpd/log.h | 46 | ||||
-rw-r--r-- | usr.sbin/lpd/logmsg.c | 161 | ||||
-rw-r--r-- | usr.sbin/lpd/lp.c | 932 | ||||
-rw-r--r-- | usr.sbin/lpd/lp.h | 163 | ||||
-rw-r--r-- | usr.sbin/lpd/lp_banner.c | 1153 | ||||
-rw-r--r-- | usr.sbin/lpd/lp_displayq.c | 287 | ||||
-rw-r--r-- | usr.sbin/lpd/lp_rmjob.c | 209 | ||||
-rw-r--r-- | usr.sbin/lpd/lp_stty.c | 543 | ||||
-rw-r--r-- | usr.sbin/lpd/lpd.c | 461 | ||||
-rw-r--r-- | usr.sbin/lpd/lpd.h | 148 | ||||
-rw-r--r-- | usr.sbin/lpd/parse.y | 975 | ||||
-rw-r--r-- | usr.sbin/lpd/printer.c | 1401 | ||||
-rw-r--r-- | usr.sbin/lpd/proc.c | 508 | ||||
-rw-r--r-- | usr.sbin/lpd/proc.h | 57 | ||||
-rw-r--r-- | usr.sbin/lpd/resolver.c | 355 |
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); |