xref: /openbsd-src/usr.sbin/lpd/frontend_lpr.c (revision af27b3cce1070d60f0e791a83d790160efdc2be8)
1*af27b3ccSclaudio /*	$OpenBSD: frontend_lpr.c,v 1.5 2024/11/21 13:34:51 claudio Exp $	*/
23b188dabSeric 
33b188dabSeric /*
43b188dabSeric  * Copyright (c) 2017 Eric Faurot <eric@openbsd.org>
53b188dabSeric  *
63b188dabSeric  * Permission to use, copy, modify, and distribute this software for any
73b188dabSeric  * purpose with or without fee is hereby granted, provided that the above
83b188dabSeric  * copyright notice and this permission notice appear in all copies.
93b188dabSeric  *
103b188dabSeric  * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
113b188dabSeric  * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
123b188dabSeric  * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
133b188dabSeric  * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
143b188dabSeric  * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
153b188dabSeric  * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
163b188dabSeric  * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
173b188dabSeric  */
183b188dabSeric 
193b188dabSeric #include <sys/types.h>
203b188dabSeric #include <sys/socket.h>
213b188dabSeric #include <netinet/in.h>
223b188dabSeric 
233b188dabSeric #include <ctype.h>
243b188dabSeric #include <errno.h>
253b188dabSeric #include <limits.h>
263b188dabSeric #include <netdb.h>
273b188dabSeric #include <stdarg.h>
283b188dabSeric #include <stdlib.h>
293b188dabSeric #include <stdio.h>
303b188dabSeric #include <string.h>
313b188dabSeric #include <unistd.h>
323b188dabSeric 
333b188dabSeric #include "lpd.h"
343b188dabSeric #include "lp.h"
353b188dabSeric 
363b188dabSeric #include "io.h"
373b188dabSeric #include "log.h"
383b188dabSeric #include "proc.h"
393b188dabSeric 
403b188dabSeric #define SERVER_TIMEOUT	30000
413b188dabSeric #define CLIENT_TIMEOUT	 5000
423b188dabSeric 
433b188dabSeric #define	MAXARG	50
443b188dabSeric 
453b188dabSeric #define	F_ZOMBIE	0x1
463b188dabSeric #define	F_WAITADDRINFO	0x2
473b188dabSeric 
483b188dabSeric #define STATE_READ_COMMAND	0
493b188dabSeric #define STATE_READ_FILE		1
503b188dabSeric 
513b188dabSeric struct lpr_conn {
523b188dabSeric 	SPLAY_ENTRY(lpr_conn)	 entry;
533b188dabSeric 	uint32_t		 id;
543b188dabSeric 	char			 hostname[NI_MAXHOST];
553b188dabSeric 	struct io		*io;
563b188dabSeric 	int			 state;
573b188dabSeric 	int			 flags;
583b188dabSeric 	int			 recvjob;
593b188dabSeric 	int			 recvcf;
603b188dabSeric 	size_t			 expect;
613b188dabSeric 	FILE			*ofp;	/* output file when receiving data */
623b188dabSeric 	int			 ifd;	/* input file for displayq/rmjob */
633b188dabSeric 
643b188dabSeric 	char			*cmd;
653b188dabSeric 	int			 ai_done;
663b188dabSeric 	struct addrinfo		*ai;
673b188dabSeric 	struct io		*iofwd;
683b188dabSeric };
693b188dabSeric 
703b188dabSeric SPLAY_HEAD(lpr_conn_tree, lpr_conn);
713b188dabSeric 
723b188dabSeric static int lpr_conn_cmp(struct lpr_conn *, struct lpr_conn *);
733b188dabSeric SPLAY_PROTOTYPE(lpr_conn_tree, lpr_conn, entry, lpr_conn_cmp);
743b188dabSeric 
753b188dabSeric static void lpr_on_allowedhost(struct lpr_conn *, const char *, const char *);
763b188dabSeric static void lpr_on_recvjob(struct lpr_conn *, int);
773b188dabSeric static void lpr_on_recvjob_file(struct lpr_conn *, int, size_t, int, int);
783b188dabSeric static void lpr_on_request(struct lpr_conn *, int, const char *, const char *);
793b188dabSeric static void lpr_on_getaddrinfo(void *, int, struct addrinfo *);
803b188dabSeric 
813b188dabSeric static void lpr_io_dispatch(struct io *, int, void *);
823b188dabSeric static int  lpr_readcommand(struct lpr_conn *);
833b188dabSeric static int  lpr_readfile(struct lpr_conn *);
843b188dabSeric static int  lpr_parsejobfilter(struct lpr_conn *, struct lp_jobfilter *,
853b188dabSeric     int, char **);
863b188dabSeric 
873b188dabSeric static void lpr_free(struct lpr_conn *);
883b188dabSeric static void lpr_close(struct lpr_conn *);
893b188dabSeric static void lpr_ack(struct lpr_conn *, char);
903b188dabSeric static void lpr_reply(struct lpr_conn *, const char *);
913b188dabSeric static void lpr_stream(struct lpr_conn *);
923b188dabSeric static void lpr_forward(struct lpr_conn *);
933b188dabSeric 
943b188dabSeric static void lpr_iofwd_dispatch(struct io *, int, void *);
953b188dabSeric 
963b188dabSeric static struct lpr_conn_tree conns;
973b188dabSeric 
983b188dabSeric void
993b188dabSeric lpr_init(void)
1003b188dabSeric {
1013b188dabSeric 	SPLAY_INIT(&conns);
1023b188dabSeric }
1033b188dabSeric 
1043b188dabSeric void
1053b188dabSeric lpr_conn(uint32_t connid, struct listener *l, int sock,
1063b188dabSeric     const struct sockaddr *sa)
1073b188dabSeric {
1083b188dabSeric 	struct lpr_conn *conn;
1093b188dabSeric 
1103b188dabSeric 	if ((conn = calloc(1, sizeof(*conn))) == NULL) {
1113b188dabSeric 		log_warn("%s: calloc", __func__);
1123b188dabSeric 		close(sock);
1133b188dabSeric 		frontend_conn_closed(connid);
1143b188dabSeric 		return;
1153b188dabSeric 	}
1163b188dabSeric 	conn->id = connid;
1173b188dabSeric 	conn->ifd = -1;
1183b188dabSeric 	conn->io = io_new();
1193b188dabSeric 	if (conn->io == NULL) {
1203b188dabSeric 		log_warn("%s: io_new", __func__);
1213b188dabSeric 		free(conn);
1223b188dabSeric 		close(sock);
1233b188dabSeric 		frontend_conn_closed(connid);
1243b188dabSeric 		return;
1253b188dabSeric 	}
1263b188dabSeric 	SPLAY_INSERT(lpr_conn_tree, &conns, conn);
1273b188dabSeric 	io_set_callback(conn->io, lpr_io_dispatch, conn);
1283b188dabSeric 	io_set_timeout(conn->io, CLIENT_TIMEOUT);
1293b188dabSeric 	io_set_write(conn->io);
1303b188dabSeric 	io_attach(conn->io, sock);
1313b188dabSeric 
1323b188dabSeric 	conn->state = STATE_READ_COMMAND;
1333b188dabSeric 	m_create(p_engine, IMSG_LPR_ALLOWEDHOST, conn->id, 0, -1);
1343b188dabSeric 	m_add_sockaddr(p_engine, sa);
1353b188dabSeric 	m_close(p_engine);
1363b188dabSeric }
1373b188dabSeric 
1383b188dabSeric void
1393b188dabSeric lpr_dispatch_engine(struct imsgproc *proc, struct imsg *imsg)
1403b188dabSeric {
1413b188dabSeric 	struct lpr_conn *conn = NULL, key;
1423b188dabSeric 	const char *hostname, *reject, *cmd;
1433b188dabSeric 	size_t sz;
1443b188dabSeric 	int ack, cf = 0;
1453b188dabSeric 
1463b188dabSeric 	key.id = imsg->hdr.peerid;
1473b188dabSeric 	if (key.id) {
1483b188dabSeric 		conn = SPLAY_FIND(lpr_conn_tree, &conns, &key);
1493b188dabSeric 		if (conn == NULL) {
1503b188dabSeric 			log_debug("%08x dead-session", key.id);
1513b188dabSeric 			return;
1523b188dabSeric 		}
1533b188dabSeric 	}
1543b188dabSeric 
1553b188dabSeric 	switch (imsg->hdr.type) {
1563b188dabSeric 	case IMSG_LPR_ALLOWEDHOST:
1573b188dabSeric 		m_get_string(proc, &hostname);
1583b188dabSeric 		m_get_string(proc, &reject);
1593b188dabSeric 		m_end(proc);
1609d855d3dSeric 		lpr_on_allowedhost(conn, hostname, reject);
1613b188dabSeric 		break;
1623b188dabSeric 
1633b188dabSeric 	case IMSG_LPR_RECVJOB:
1643b188dabSeric 		m_get_int(proc, &ack);
1653b188dabSeric 		m_end(proc);
1663b188dabSeric 		lpr_on_recvjob(conn, ack);
1673b188dabSeric 		break;
1683b188dabSeric 
1693b188dabSeric 	case IMSG_LPR_RECVJOB_CF:
1703b188dabSeric 		cf = 1;
1713b188dabSeric 	case IMSG_LPR_RECVJOB_DF:
1723b188dabSeric 		m_get_int(proc, &ack);
1733b188dabSeric 		m_get_size(proc, &sz);
1743b188dabSeric 		m_end(proc);
175*af27b3ccSclaudio 		lpr_on_recvjob_file(conn, ack, sz, cf, imsg_get_fd(imsg));
1763b188dabSeric 		break;
1773b188dabSeric 
1783b188dabSeric 	case IMSG_LPR_DISPLAYQ:
1793b188dabSeric 	case IMSG_LPR_RMJOB:
1803b188dabSeric 		m_get_string(proc, &hostname);
1813b188dabSeric 		m_get_string(proc, &cmd);
1823b188dabSeric 		m_end(proc);
183*af27b3ccSclaudio 		lpr_on_request(conn, imsg_get_fd(imsg), hostname, cmd);
1843b188dabSeric 		break;
1853b188dabSeric 
1863b188dabSeric 	default:
1873b188dabSeric 		fatalx("%s: unexpected imsg %s", __func__,
1883b188dabSeric 		    log_fmt_imsgtype(imsg->hdr.type));
1893b188dabSeric 	}
1903b188dabSeric }
1913b188dabSeric 
1923b188dabSeric static void
1933b188dabSeric lpr_on_allowedhost(struct lpr_conn *conn, const char *hostname,
1943b188dabSeric     const char *reject)
1953b188dabSeric {
1963b188dabSeric 	strlcpy(conn->hostname, hostname, sizeof(conn->hostname));
1973b188dabSeric 	if (reject)
1983b188dabSeric 		lpr_reply(conn, reject);
1993b188dabSeric 	else
2003b188dabSeric 		io_set_read(conn->io);
2013b188dabSeric }
2023b188dabSeric 
2033b188dabSeric static void
2043b188dabSeric lpr_on_recvjob(struct lpr_conn *conn, int ack)
2053b188dabSeric {
2063b188dabSeric 	if (ack == LPR_ACK)
2073b188dabSeric 		conn->recvjob = 1;
2083b188dabSeric 	else
2093b188dabSeric 		log_debug("%08x recvjob failed", conn->id);
2103b188dabSeric 	lpr_ack(conn, ack);
2113b188dabSeric }
2123b188dabSeric 
2133b188dabSeric static void
2143b188dabSeric lpr_on_recvjob_file(struct lpr_conn *conn, int ack, size_t sz, int cf, int fd)
2153b188dabSeric {
2163b188dabSeric 	if (ack != LPR_ACK) {
2173b188dabSeric 		lpr_ack(conn, ack);
2183b188dabSeric 		return;
2193b188dabSeric 	}
2203b188dabSeric 
2213b188dabSeric 	if (fd == -1) {
2223b188dabSeric 		log_warnx("%s: failed to get fd", __func__);
2233b188dabSeric 		lpr_ack(conn, LPR_NACK);
2243b188dabSeric 		return;
2253b188dabSeric 	}
2263b188dabSeric 
2273b188dabSeric 	conn->ofp = fdopen(fd, "w");
2283b188dabSeric 	if (conn->ofp == NULL) {
2293b188dabSeric 		log_warn("%s: fdopen", __func__);
2303b188dabSeric 		close(fd);
2313b188dabSeric 		lpr_ack(conn, LPR_NACK);
2323b188dabSeric 		return;
2333b188dabSeric 	}
2343b188dabSeric 
2353b188dabSeric 	conn->expect = sz;
2363b188dabSeric 	if (cf)
2373b188dabSeric 		conn->recvcf = cf;
2383b188dabSeric 	conn->state = STATE_READ_FILE;
2393b188dabSeric 
2403b188dabSeric 	lpr_ack(conn, LPR_ACK);
2413b188dabSeric }
2423b188dabSeric 
2433b188dabSeric static void
2443b188dabSeric lpr_on_request(struct lpr_conn *conn, int fd, const char *hostname,
2453b188dabSeric     const char *cmd)
2463b188dabSeric {
2473b188dabSeric 	struct addrinfo hints;
2483b188dabSeric 
2493b188dabSeric 	if (fd == -1) {
2503b188dabSeric 		log_warnx("%s: no fd received", __func__);
2513b188dabSeric 		lpr_close(conn);
2523b188dabSeric 		return;
2533b188dabSeric 	}
2543b188dabSeric 
2553b188dabSeric 	log_debug("%08x stream init", conn->id);
2563b188dabSeric 	conn->ifd = fd;
2573b188dabSeric 
2583b188dabSeric 	/* Prepare for command forwarding if necessary. */
2593b188dabSeric 	if (cmd) {
2603b188dabSeric 		log_debug("%08x forwarding to %s: \\%d%s", conn->id, hostname,
2613b188dabSeric 		    cmd[0], cmd + 1);
2623b188dabSeric 		conn->cmd = strdup(cmd);
2633b188dabSeric 		if (conn->cmd == NULL)
2643b188dabSeric 			log_warn("%s: strdup", __func__);
2653b188dabSeric 		else {
2663b188dabSeric 			memset(&hints, 0, sizeof(hints));
2673b188dabSeric 			hints.ai_socktype = SOCK_STREAM;
2683b188dabSeric 			conn->flags |= F_WAITADDRINFO;
2693b188dabSeric 			/*
2703a50f0a9Sjmc 			 * The callback might run immediately, so conn->ifd
2713b188dabSeric 			 * must be set before, to block lpr_forward().
2723b188dabSeric 			 */
2733b188dabSeric 			resolver_getaddrinfo(hostname, "printer", &hints,
2743b188dabSeric 			    lpr_on_getaddrinfo, conn);
2753b188dabSeric 		}
2763b188dabSeric 	}
2773b188dabSeric 
2783b188dabSeric 	lpr_stream(conn);
2793b188dabSeric }
2803b188dabSeric 
2813b188dabSeric static void
2823b188dabSeric lpr_on_getaddrinfo(void *arg, int r, struct addrinfo *ai)
2833b188dabSeric {
2843b188dabSeric 	struct lpr_conn *conn = arg;
2853b188dabSeric 
2863b188dabSeric 	conn->flags &= ~F_WAITADDRINFO;
2873b188dabSeric 	if (conn->flags & F_ZOMBIE) {
2883b188dabSeric 		if (ai)
2893b188dabSeric 			freeaddrinfo(ai);
2903b188dabSeric 		lpr_free(conn);
2913b188dabSeric 	}
2923b188dabSeric 	else {
2933b188dabSeric 		conn->ai_done = 1;
2943b188dabSeric 		conn->ai = ai;
2953b188dabSeric 		lpr_forward(conn);
2963b188dabSeric 	}
2973b188dabSeric }
2983b188dabSeric 
2993b188dabSeric static void
3003b188dabSeric lpr_io_dispatch(struct io *io, int evt, void *arg)
3013b188dabSeric {
3023b188dabSeric 	struct lpr_conn *conn = arg;
3033b188dabSeric 	int r;
3043b188dabSeric 
3053b188dabSeric 	switch (evt) {
3063b188dabSeric 	case IO_DATAIN:
3073b188dabSeric 		switch(conn->state) {
3083b188dabSeric 		case STATE_READ_COMMAND:
3093b188dabSeric 			r = lpr_readcommand(conn);
3103b188dabSeric 			break;
3113b188dabSeric 		case STATE_READ_FILE:
3123b188dabSeric 			r = lpr_readfile(conn);
3133b188dabSeric 			break;
3143b188dabSeric 		default:
3153b188dabSeric 			fatal("%s: unexpected state %d", __func__, conn->state);
3163b188dabSeric 		}
3173b188dabSeric 
3183b188dabSeric 		if (r == 0)
3193b188dabSeric 			io_set_write(conn->io);
3203b188dabSeric 		return;
3213b188dabSeric 
3223b188dabSeric 	case IO_LOWAT:
3233b188dabSeric 		if (conn->recvjob)
3243b188dabSeric 			io_set_read(conn->io);
3253b188dabSeric 		else if (conn->ifd != -1)
3263b188dabSeric 			lpr_stream(conn);
3273b188dabSeric 		else if (conn->cmd == NULL)
3283b188dabSeric 			lpr_close(conn);
3293b188dabSeric 		return;
3303b188dabSeric 
3313b188dabSeric 	case IO_DISCONNECTED:
3323b188dabSeric 		log_debug("%08x disconnected", conn->id);
3333b188dabSeric 		/*
3343b188dabSeric 		 * Some clients don't wait for the last acknowledgment to close
3353b188dabSeric 		 * the session.  So just consider it is closed normally.
3363b188dabSeric 		 */
3373b188dabSeric 	case IO_CLOSED:
3383b188dabSeric 		if (conn->recvcf && conn->state == STATE_READ_COMMAND) {
3393b188dabSeric 			/*
3403b188dabSeric 			 * Commit the transaction if we received a control file
3413b188dabSeric 			 * and the last file was received correctly.
3423b188dabSeric 			 */
3433b188dabSeric 			m_compose(p_engine, IMSG_LPR_RECVJOB_COMMIT, conn->id,
3443b188dabSeric 			    0, -1, NULL, 0);
3453b188dabSeric 			conn->recvjob = 0;
3463b188dabSeric 		}
3473b188dabSeric 		break;
3483b188dabSeric 
3493b188dabSeric 	case IO_TIMEOUT:
3503b188dabSeric 		log_debug("%08x timeout", conn->id);
3513b188dabSeric 		break;
3523b188dabSeric 
3533b188dabSeric 	case IO_ERROR:
3543b188dabSeric 		log_debug("%08x io-error", conn->id);
3553b188dabSeric 		break;
3563b188dabSeric 
3573b188dabSeric 	default:
3583b188dabSeric 		fatalx("%s: unexpected event %d", __func__, evt);
3593b188dabSeric 	}
3603b188dabSeric 
3613b188dabSeric 	lpr_close(conn);
3623b188dabSeric }
3633b188dabSeric 
3643b188dabSeric static int
3653b188dabSeric lpr_readcommand(struct lpr_conn *conn)
3663b188dabSeric {
3673b188dabSeric 	struct lp_jobfilter jf;
3683b188dabSeric 	size_t count;
3693b188dabSeric 	const char *errstr;
3703b188dabSeric 	char *argv[MAXARG], *line;
3713b188dabSeric 	int i, argc, cmd;
3723b188dabSeric 
3733b188dabSeric 	line = io_getline(conn->io, NULL);
3743b188dabSeric 	if (line == NULL) {
3753b188dabSeric 		if (io_datalen(conn->io) >= LPR_MAXCMDLEN) {
3763b188dabSeric 			lpr_reply(conn, "Request line too long");
3773b188dabSeric 			return 0;
3783b188dabSeric 		}
3793b188dabSeric 		return -1;
3803b188dabSeric 	}
3813b188dabSeric 
3823b188dabSeric 	cmd = line[0];
3833b188dabSeric 	line++;
3843b188dabSeric 
3853b188dabSeric 	if (cmd == 0) {
3863b188dabSeric 		lpr_reply(conn, "No command");
3873b188dabSeric 		return 0;
3883b188dabSeric 	}
3893b188dabSeric 
3903b188dabSeric 	log_debug("%08x cmd \\%d", conn->id, cmd);
3913b188dabSeric 
3923b188dabSeric 	/* Parse the command. */
3933b188dabSeric 	for (argc = 0; argc < MAXARG; ) {
3943b188dabSeric 		argv[argc] = strsep(&line, " \t");
3953b188dabSeric 		if (argv[argc] == NULL)
3963b188dabSeric 			break;
3973b188dabSeric 		if (argv[argc][0] != '\0')
3983b188dabSeric 			argc++;
3993b188dabSeric 	}
4003b188dabSeric 	if (argc == MAXARG) {
4013b188dabSeric 		lpr_reply(conn, "Argument list too long");
4023b188dabSeric 		return 0;
4033b188dabSeric 	}
4043b188dabSeric 
4053b188dabSeric 	if (argc == 0) {
4063b188dabSeric 		lpr_reply(conn, "No queue specified");
4073b188dabSeric 		return 0;
4083b188dabSeric 	}
4093b188dabSeric 
4103b188dabSeric #define CMD(c)    ((int)(c))
4113b188dabSeric #define SUBCMD(c) (0x100 | (int)(c))
4123b188dabSeric 
4133b188dabSeric 	if (conn->recvjob)
4143b188dabSeric 		cmd |= 0x100;
4153b188dabSeric 	switch (cmd) {
4163b188dabSeric 	case CMD('\1'):	/* PRINT <prn> */
4173b188dabSeric 		m_create(p_engine, IMSG_LPR_PRINTJOB, 0, 0, -1);
4183b188dabSeric 		m_add_string(p_engine, argv[0]);
4193b188dabSeric 		m_close(p_engine);
4203b188dabSeric 		lpr_ack(conn, LPR_ACK);
4213b188dabSeric 		return 0;
4223b188dabSeric 
4233b188dabSeric 	case CMD('\2'):	/* RECEIVE JOB <prn> */
4243b188dabSeric 		m_create(p_engine, IMSG_LPR_RECVJOB, conn->id, 0, -1);
4253b188dabSeric 		m_add_string(p_engine, conn->hostname);
4263b188dabSeric 		m_add_string(p_engine, argv[0]);
4273b188dabSeric 		m_close(p_engine);
4283b188dabSeric 		return 0;
4293b188dabSeric 
4303b188dabSeric 	case CMD('\3'):	/* QUEUE STATE SHORT <prn> [job#...] [user..] */
4313b188dabSeric 	case CMD('\4'):	/* QUEUE STATE LONG  <prn> [job#...] [user..] */
4323b188dabSeric 		if (lpr_parsejobfilter(conn, &jf, argc - 1, argv + 1) == -1)
4333b188dabSeric 			return 0;
4343b188dabSeric 
4353b188dabSeric 		m_create(p_engine, IMSG_LPR_DISPLAYQ, conn->id, 0, -1);
4363b188dabSeric 		m_add_int(p_engine, (cmd == '\3') ? 0 : 1);
4373b188dabSeric 		m_add_string(p_engine, conn->hostname);
4383b188dabSeric 		m_add_string(p_engine, argv[0]);
4393b188dabSeric 		m_add_int(p_engine, jf.njob);
4403b188dabSeric 		for (i = 0; i < jf.njob; i++)
4413b188dabSeric 			m_add_int(p_engine, jf.jobs[i]);
4423b188dabSeric 		m_add_int(p_engine, jf.nuser);
4433b188dabSeric 		for (i = 0; i < jf.nuser; i++)
4443b188dabSeric 			m_add_string(p_engine, jf.users[i]);
4453b188dabSeric 		m_close(p_engine);
4463b188dabSeric 		return 0;
4473b188dabSeric 
4483b188dabSeric 	case CMD('\5'):	/* REMOVE JOBS <prn> <agent> [job#...] [user..] */
4493b188dabSeric 		if (argc < 2) {
4503b188dabSeric 			lpr_reply(conn, "No agent specified");
4513b188dabSeric 			return 0;
4523b188dabSeric 		}
4533b188dabSeric 		if (lpr_parsejobfilter(conn, &jf, argc - 2, argv + 2) == -1)
4543b188dabSeric 			return 0;
4553b188dabSeric 
4563b188dabSeric 		m_create(p_engine, IMSG_LPR_RMJOB, conn->id, 0, -1);
4573b188dabSeric 		m_add_string(p_engine, conn->hostname);
4583b188dabSeric 		m_add_string(p_engine, argv[0]);
4593b188dabSeric 		m_add_string(p_engine, argv[1]);
4603b188dabSeric 		m_add_int(p_engine, jf.njob);
4613b188dabSeric 		for (i = 0; i < jf.njob; i++)
4623b188dabSeric 			m_add_int(p_engine, jf.jobs[i]);
4633b188dabSeric 		m_add_int(p_engine, jf.nuser);
4643b188dabSeric 		for (i = 0; i < jf.nuser; i++)
4653b188dabSeric 			m_add_string(p_engine, jf.users[i]);
4663b188dabSeric 		m_close(p_engine);
4673b188dabSeric 		return 0;
4683b188dabSeric 
4693b188dabSeric 	case SUBCMD('\1'):	/* ABORT */
4703b188dabSeric 		m_compose(p_engine, IMSG_LPR_RECVJOB_CLEAR, conn->id, 0, -1,
4713b188dabSeric 		    NULL, 0);
4723b188dabSeric 		conn->recvcf = 0;
4733b188dabSeric 		lpr_ack(conn, LPR_ACK);
4743b188dabSeric 		return 0;
4753b188dabSeric 
4763b188dabSeric 	case SUBCMD('\2'):	/* CONTROL FILE <size> <filename> */
4773b188dabSeric 	case SUBCMD('\3'):	/* DATA FILE    <size> <filename> */
4783b188dabSeric 		if (argc != 2) {
4793b188dabSeric 			log_debug("%08x invalid number of argument", conn->id);
4803b188dabSeric 			lpr_ack(conn, LPR_NACK);
4813b188dabSeric 			return 0;
4823b188dabSeric 		}
4833b188dabSeric 		errstr = NULL;
4843b188dabSeric 		count = strtonum(argv[0], 1, LPR_MAXFILESIZE, &errstr);
4853b188dabSeric 		if (errstr) {
4863b188dabSeric 			log_debug("%08x invalid file size: %s", conn->id,
4873b188dabSeric 			    strerror(errno));
4883b188dabSeric 			lpr_ack(conn, LPR_NACK);
4893b188dabSeric 			return 0;
4903b188dabSeric 		}
4913b188dabSeric 
4923b188dabSeric 		if (cmd == SUBCMD('\2')) {
4933b188dabSeric 			if (conn->recvcf) {
4943b188dabSeric 				log_debug("%08x cf file already received",
4953b188dabSeric 				    conn->id);
4963b188dabSeric 				lpr_ack(conn, LPR_NACK);
4973b188dabSeric 				return 0;
4983b188dabSeric 			}
4993b188dabSeric 			m_create(p_engine, IMSG_LPR_RECVJOB_CF, conn->id, 0,
5003b188dabSeric 			    -1);
5013b188dabSeric 		}
5023b188dabSeric 		else
5033b188dabSeric 			m_create(p_engine, IMSG_LPR_RECVJOB_DF, conn->id, 0,
5043b188dabSeric 			    -1);
5053b188dabSeric 		m_add_size(p_engine, count);
5063b188dabSeric 		m_add_string(p_engine, argv[1]);
5073b188dabSeric 		m_close(p_engine);
5083b188dabSeric 		return 0;
5093b188dabSeric 
5103b188dabSeric 	default:
5113b188dabSeric 		if (conn->recvjob)
5123b188dabSeric 			lpr_reply(conn, "Protocol error");
5133b188dabSeric 		else
5143b188dabSeric 			lpr_reply(conn, "Illegal service request");
5153b188dabSeric 		return 0;
5163b188dabSeric 	}
5173b188dabSeric }
5183b188dabSeric 
5193b188dabSeric static int
5203b188dabSeric lpr_readfile(struct lpr_conn *conn)
5213b188dabSeric {
5223b188dabSeric 	size_t len, w;
5233b188dabSeric 	char *data;
5243b188dabSeric 
5253b188dabSeric 	if (conn->expect) {
5263b188dabSeric 		/* Read file content. */
5273b188dabSeric 		data = io_data(conn->io);
5283b188dabSeric 		len = io_datalen(conn->io);
5293b188dabSeric 		if (len > conn->expect)
5303b188dabSeric 			len = conn->expect;
5313b188dabSeric 
5323b188dabSeric 		log_debug("%08x %zu bytes received", conn->id, len);
5333b188dabSeric 
5343b188dabSeric 		w = fwrite(data, 1, len, conn->ofp);
5353b188dabSeric 		if (w != len) {
5363b188dabSeric 			log_warnx("%s: fwrite", __func__);
5373b188dabSeric 			lpr_close(conn);
5383b188dabSeric 			return -1;
5393b188dabSeric 		}
5403b188dabSeric 		io_drop(conn->io, w);
5413b188dabSeric 		conn->expect -= w;
5423b188dabSeric 		if (conn->expect)
5433b188dabSeric 			return -1;
5443b188dabSeric 
5453b188dabSeric 		fclose(conn->ofp);
5463b188dabSeric 		conn->ofp = NULL;
5473b188dabSeric 
5483b188dabSeric 		log_debug("%08x file received", conn->id);
5493b188dabSeric 	}
5503b188dabSeric 
5513b188dabSeric 	/* Try to read '\0'. */
5523b188dabSeric 	len = io_datalen(conn->io);
5533b188dabSeric 	if (len == 0)
5543b188dabSeric 		return -1;
5553b188dabSeric 	data = io_data(conn->io);
5563b188dabSeric 	io_drop(conn->io, 1);
5573b188dabSeric 
5583b188dabSeric 	log_debug("%08x eof %d", conn->id, (int)*data);
5593b188dabSeric 
5603b188dabSeric 	if (*data != '\0') {
5613b188dabSeric 		lpr_close(conn);
5623b188dabSeric 		return -1;
5633b188dabSeric 	}
5643b188dabSeric 
5653b188dabSeric 	conn->state = STATE_READ_COMMAND;
5663b188dabSeric 	lpr_ack(conn, LPR_ACK);
5673b188dabSeric 	return 0;
5683b188dabSeric }
5693b188dabSeric 
5703b188dabSeric static int
5713b188dabSeric lpr_parsejobfilter(struct lpr_conn *conn, struct lp_jobfilter *jf, int argc,
5723b188dabSeric     char **argv)
5733b188dabSeric {
5743b188dabSeric 	const char *errstr;
5753b188dabSeric 	char *arg;
5763b188dabSeric 	int i, jobnum;
5773b188dabSeric 
5783b188dabSeric 	memset(jf, 0, sizeof(*jf));
5793b188dabSeric 
5803b188dabSeric 	for (i = 0; i < argc; i++) {
5813b188dabSeric 		arg = argv[i];
582895f7d11Sbenno 		if (isdigit((unsigned char)arg[0])) {
5833b188dabSeric 			if (jf->njob == LP_MAXREQUESTS) {
5843b188dabSeric 				lpr_reply(conn, "Too many requests");
5853b188dabSeric 				return -1;
5863b188dabSeric 			}
5873b188dabSeric 			errstr = NULL;
5883b188dabSeric 			jobnum = strtonum(arg, 0, INT_MAX, &errstr);
5893b188dabSeric 			if (errstr) {
5903b188dabSeric 				lpr_reply(conn, "Invalid job number");
5913b188dabSeric 				return -1;
5923b188dabSeric 			}
5933b188dabSeric 			jf->jobs[jf->njob++] = jobnum;
5943b188dabSeric 		}
5953b188dabSeric 		else {
5963b188dabSeric 			if (jf->nuser == LP_MAXUSERS) {
5973b188dabSeric 				lpr_reply(conn, "Too many users");
5983b188dabSeric 				return -1;
5993b188dabSeric 			}
6003b188dabSeric 			jf->users[jf->nuser++] = arg;
6013b188dabSeric 		}
6023b188dabSeric 	}
6033b188dabSeric 
6043b188dabSeric 	return 0;
6053b188dabSeric }
6063b188dabSeric 
6073b188dabSeric static void
6083b188dabSeric lpr_free(struct lpr_conn *conn)
6093b188dabSeric {
6103b188dabSeric 	if ((conn->flags & F_WAITADDRINFO) == 0)
6113b188dabSeric 		free(conn);
6123b188dabSeric }
6133b188dabSeric 
6143b188dabSeric static void
6153b188dabSeric lpr_close(struct lpr_conn *conn)
6163b188dabSeric {
6173b188dabSeric 	uint32_t connid = conn->id;
6183b188dabSeric 
6193b188dabSeric 	SPLAY_REMOVE(lpr_conn_tree, &conns, conn);
6203b188dabSeric 
6213b188dabSeric 	if (conn->recvjob)
6223b188dabSeric 		m_compose(p_engine, IMSG_LPR_RECVJOB_ROLLBACK, conn->id, 0, -1,
6233b188dabSeric 		    NULL, 0);
6243b188dabSeric 
6253b188dabSeric 	io_free(conn->io);
6263b188dabSeric 	free(conn->cmd);
6273b188dabSeric 	if (conn->ofp)
6283b188dabSeric 		fclose(conn->ofp);
6293b188dabSeric 	if (conn->ifd != -1)
6303b188dabSeric 		close(conn->ifd);
6313b188dabSeric 	if (conn->ai)
6323b188dabSeric 		freeaddrinfo(conn->ai);
6333b188dabSeric 	if (conn->iofwd)
6343b188dabSeric 		io_free(conn->iofwd);
6353b188dabSeric 
6363b188dabSeric 	conn->flags |= F_ZOMBIE;
6373b188dabSeric 	lpr_free(conn);
6383b188dabSeric 
6393b188dabSeric 	frontend_conn_closed(connid);
6403b188dabSeric }
6413b188dabSeric 
6423b188dabSeric static void
6433b188dabSeric lpr_ack(struct lpr_conn *conn, char c)
6443b188dabSeric {
6453b188dabSeric 	if (c == 0)
6463b188dabSeric 		log_debug("%08x ack", conn->id);
6473b188dabSeric 	else
6483b188dabSeric 		log_debug("%08x nack %d", conn->id, (int)c);
6493b188dabSeric 
6503b188dabSeric 	io_write(conn->io, &c, 1);
6513b188dabSeric }
6523b188dabSeric 
6533b188dabSeric static void
6543b188dabSeric lpr_reply(struct lpr_conn *conn, const char *s)
6553b188dabSeric {
6563b188dabSeric 	log_debug("%08x reply: %s", conn->id, s);
6573b188dabSeric 
6583b188dabSeric 	io_printf(conn->io, "%s\n", s);
6593b188dabSeric }
6603b188dabSeric 
6613b188dabSeric /*
6623a50f0a9Sjmc  * Stream response file to the client.
6633b188dabSeric  */
6643b188dabSeric static void
6653b188dabSeric lpr_stream(struct lpr_conn *conn)
6663b188dabSeric {
6673b188dabSeric 	char buf[BUFSIZ];
6683b188dabSeric 	ssize_t r;
6693b188dabSeric 
6703b188dabSeric 	for (;;) {
6713b188dabSeric 		if (io_queued(conn->io) > 65536)
6723b188dabSeric 			return;
6733b188dabSeric 
6743b188dabSeric 		r = read(conn->ifd, buf, sizeof(buf));
6753b188dabSeric 		if (r == -1) {
6763b188dabSeric 			if (errno == EINTR)
6773b188dabSeric 				continue;
6783b188dabSeric 			log_warn("%s: read", __func__);
6793b188dabSeric 			break;
6803b188dabSeric 		}
6813b188dabSeric 
6823b188dabSeric 		if (r == 0) {
6833b188dabSeric 			log_debug("%08x stream done", conn->id);
6843b188dabSeric 			break;
6853b188dabSeric 		}
6863b188dabSeric 		log_debug("%08x stream %zu bytes", conn->id, r);
6873b188dabSeric 
6883b188dabSeric 		if (io_write(conn->io, buf, r) == -1) {
6893b188dabSeric 			log_warn("%s: io_write", __func__);
6903b188dabSeric 			break;
6913b188dabSeric 		}
6923b188dabSeric 	}
6933b188dabSeric 
6943b188dabSeric 	close(conn->ifd);
6953b188dabSeric 	conn->ifd = -1;
6963b188dabSeric 
6973b188dabSeric 	if (conn->cmd)
6983b188dabSeric 		lpr_forward(conn);
6993b188dabSeric 
7003b188dabSeric 	else if (io_queued(conn->io) == 0)
7013b188dabSeric 		lpr_close(conn);
7023b188dabSeric }
7033b188dabSeric 
7043b188dabSeric /*
7053b188dabSeric  * Forward request to the remote printer.
7063b188dabSeric  */
7073b188dabSeric static void
7083b188dabSeric lpr_forward(struct lpr_conn *conn)
7093b188dabSeric {
7103b188dabSeric 	/*
7113b188dabSeric 	 * Do not start forwarding the command if the address is not resolved
7123b188dabSeric 	 * or if the local response is still being sent to the client.
7133b188dabSeric 	 */
7143b188dabSeric 	if (!conn->ai_done || conn->ifd == -1)
7153b188dabSeric 		return;
7163b188dabSeric 
7173b188dabSeric 	if (conn->ai == NULL) {
7183b188dabSeric 		if (io_queued(conn->io) == 0)
7193b188dabSeric 			lpr_close(conn);
7203b188dabSeric 		return;
7213b188dabSeric 	}
7223b188dabSeric 
7233b188dabSeric 	log_debug("%08x forward start", conn->id);
7243b188dabSeric 
7253b188dabSeric 	conn->iofwd = io_new();
7263b188dabSeric 	if (conn->iofwd == NULL) {
7273b188dabSeric 		log_warn("%s: io_new", __func__);
7283b188dabSeric 		if (io_queued(conn->io) == 0)
7293b188dabSeric 			lpr_close(conn);
7303b188dabSeric 		return;
7313b188dabSeric 	}
7323b188dabSeric 	io_set_callback(conn->iofwd, lpr_iofwd_dispatch, conn);
7333b188dabSeric 	io_set_timeout(conn->io, SERVER_TIMEOUT);
7343b188dabSeric 	io_connect(conn->iofwd, conn->ai);
7353b188dabSeric 	conn->ai = NULL;
7363b188dabSeric }
7373b188dabSeric 
7383b188dabSeric static void
7393b188dabSeric lpr_iofwd_dispatch(struct io *io, int evt, void *arg)
7403b188dabSeric {
7413b188dabSeric 	struct lpr_conn *conn = arg;
7423b188dabSeric 
7433b188dabSeric 	switch (evt) {
7443b188dabSeric 	case IO_CONNECTED:
7453b188dabSeric 		log_debug("%08x forward connected", conn->id);
7463b188dabSeric 		/* Send the request. */
7473b188dabSeric 		io_print(io, conn->cmd);
7483b188dabSeric 		io_print(io, "\n");
7493b188dabSeric 		io_set_write(io);
7503b188dabSeric 		return;
7513b188dabSeric 
7523b188dabSeric 	case IO_DATAIN:
7533b188dabSeric 		/* Relay. */
7543b188dabSeric 		io_write(conn->io, io_data(io), io_datalen(io));
7553b188dabSeric 		io_drop(io, io_datalen(io));
7563b188dabSeric 		return;
7573b188dabSeric 
7583b188dabSeric 	case IO_LOWAT:
7593b188dabSeric 		/* Read response. */
7603b188dabSeric 		io_set_read(io);
7613b188dabSeric 		return;
7623b188dabSeric 
7633b188dabSeric 	case IO_CLOSED:
7643b188dabSeric 		break;
7653b188dabSeric 
7663b188dabSeric 	case IO_DISCONNECTED:
7673b188dabSeric 		log_debug("%08x forward disconnected", conn->id);
7683b188dabSeric 		break;
7693b188dabSeric 
7703b188dabSeric 	case IO_TIMEOUT:
7713b188dabSeric 		log_debug("%08x forward timeout", conn->id);
7723b188dabSeric 		break;
7733b188dabSeric 
7743b188dabSeric 	case IO_ERROR:
7753b188dabSeric 		log_debug("%08x forward io-error", conn->id);
7763b188dabSeric 		break;
7773b188dabSeric 
7783b188dabSeric 	default:
7793b188dabSeric 		fatalx("%s: unexpected event %d", __func__, evt);
7803b188dabSeric 	}
7813b188dabSeric 
7823b188dabSeric 	log_debug("%08x forward done", conn->id);
7833b188dabSeric 
7843b188dabSeric 	io_free(io);
7853b188dabSeric 	free(conn->cmd);
7863b188dabSeric 	conn->cmd = NULL;
7873b188dabSeric 	conn->iofwd = NULL;
7883b188dabSeric 	if (io_queued(conn->io) == 0)
7893b188dabSeric 		lpr_close(conn);
7903b188dabSeric }
7913b188dabSeric 
7923b188dabSeric static int
7933b188dabSeric lpr_conn_cmp(struct lpr_conn *a, struct lpr_conn *b)
7943b188dabSeric {
7953b188dabSeric 	if (a->id < b->id)
7963b188dabSeric 		return -1;
7973b188dabSeric 	if (a->id > b->id)
7983b188dabSeric 		return 1;
7993b188dabSeric 	return 0;
8003b188dabSeric }
8013b188dabSeric 
8023b188dabSeric SPLAY_GENERATE(lpr_conn_tree, lpr_conn, entry, lpr_conn_cmp);
803