xref: /openbsd/usr.sbin/lpd/frontend_lpr.c (revision af27b3cc)
1 /*	$OpenBSD: frontend_lpr.c,v 1.5 2024/11/21 13:34:51 claudio Exp $	*/
2 
3 /*
4  * Copyright (c) 2017 Eric Faurot <eric@openbsd.org>
5  *
6  * Permission to use, copy, modify, and distribute this software for any
7  * purpose with or without fee is hereby granted, provided that the above
8  * copyright notice and this permission notice appear in all copies.
9  *
10  * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
11  * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
12  * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
13  * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
14  * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
15  * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
16  * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
17  */
18 
19 #include <sys/types.h>
20 #include <sys/socket.h>
21 #include <netinet/in.h>
22 
23 #include <ctype.h>
24 #include <errno.h>
25 #include <limits.h>
26 #include <netdb.h>
27 #include <stdarg.h>
28 #include <stdlib.h>
29 #include <stdio.h>
30 #include <string.h>
31 #include <unistd.h>
32 
33 #include "lpd.h"
34 #include "lp.h"
35 
36 #include "io.h"
37 #include "log.h"
38 #include "proc.h"
39 
40 #define SERVER_TIMEOUT	30000
41 #define CLIENT_TIMEOUT	 5000
42 
43 #define	MAXARG	50
44 
45 #define	F_ZOMBIE	0x1
46 #define	F_WAITADDRINFO	0x2
47 
48 #define STATE_READ_COMMAND	0
49 #define STATE_READ_FILE		1
50 
51 struct lpr_conn {
52 	SPLAY_ENTRY(lpr_conn)	 entry;
53 	uint32_t		 id;
54 	char			 hostname[NI_MAXHOST];
55 	struct io		*io;
56 	int			 state;
57 	int			 flags;
58 	int			 recvjob;
59 	int			 recvcf;
60 	size_t			 expect;
61 	FILE			*ofp;	/* output file when receiving data */
62 	int			 ifd;	/* input file for displayq/rmjob */
63 
64 	char			*cmd;
65 	int			 ai_done;
66 	struct addrinfo		*ai;
67 	struct io		*iofwd;
68 };
69 
70 SPLAY_HEAD(lpr_conn_tree, lpr_conn);
71 
72 static int lpr_conn_cmp(struct lpr_conn *, struct lpr_conn *);
73 SPLAY_PROTOTYPE(lpr_conn_tree, lpr_conn, entry, lpr_conn_cmp);
74 
75 static void lpr_on_allowedhost(struct lpr_conn *, const char *, const char *);
76 static void lpr_on_recvjob(struct lpr_conn *, int);
77 static void lpr_on_recvjob_file(struct lpr_conn *, int, size_t, int, int);
78 static void lpr_on_request(struct lpr_conn *, int, const char *, const char *);
79 static void lpr_on_getaddrinfo(void *, int, struct addrinfo *);
80 
81 static void lpr_io_dispatch(struct io *, int, void *);
82 static int  lpr_readcommand(struct lpr_conn *);
83 static int  lpr_readfile(struct lpr_conn *);
84 static int  lpr_parsejobfilter(struct lpr_conn *, struct lp_jobfilter *,
85     int, char **);
86 
87 static void lpr_free(struct lpr_conn *);
88 static void lpr_close(struct lpr_conn *);
89 static void lpr_ack(struct lpr_conn *, char);
90 static void lpr_reply(struct lpr_conn *, const char *);
91 static void lpr_stream(struct lpr_conn *);
92 static void lpr_forward(struct lpr_conn *);
93 
94 static void lpr_iofwd_dispatch(struct io *, int, void *);
95 
96 static struct lpr_conn_tree conns;
97 
98 void
lpr_init(void)99 lpr_init(void)
100 {
101 	SPLAY_INIT(&conns);
102 }
103 
104 void
lpr_conn(uint32_t connid,struct listener * l,int sock,const struct sockaddr * sa)105 lpr_conn(uint32_t connid, struct listener *l, int sock,
106     const struct sockaddr *sa)
107 {
108 	struct lpr_conn *conn;
109 
110 	if ((conn = calloc(1, sizeof(*conn))) == NULL) {
111 		log_warn("%s: calloc", __func__);
112 		close(sock);
113 		frontend_conn_closed(connid);
114 		return;
115 	}
116 	conn->id = connid;
117 	conn->ifd = -1;
118 	conn->io = io_new();
119 	if (conn->io == NULL) {
120 		log_warn("%s: io_new", __func__);
121 		free(conn);
122 		close(sock);
123 		frontend_conn_closed(connid);
124 		return;
125 	}
126 	SPLAY_INSERT(lpr_conn_tree, &conns, conn);
127 	io_set_callback(conn->io, lpr_io_dispatch, conn);
128 	io_set_timeout(conn->io, CLIENT_TIMEOUT);
129 	io_set_write(conn->io);
130 	io_attach(conn->io, sock);
131 
132 	conn->state = STATE_READ_COMMAND;
133 	m_create(p_engine, IMSG_LPR_ALLOWEDHOST, conn->id, 0, -1);
134 	m_add_sockaddr(p_engine, sa);
135 	m_close(p_engine);
136 }
137 
138 void
lpr_dispatch_engine(struct imsgproc * proc,struct imsg * imsg)139 lpr_dispatch_engine(struct imsgproc *proc, struct imsg *imsg)
140 {
141 	struct lpr_conn *conn = NULL, key;
142 	const char *hostname, *reject, *cmd;
143 	size_t sz;
144 	int ack, cf = 0;
145 
146 	key.id = imsg->hdr.peerid;
147 	if (key.id) {
148 		conn = SPLAY_FIND(lpr_conn_tree, &conns, &key);
149 		if (conn == NULL) {
150 			log_debug("%08x dead-session", key.id);
151 			return;
152 		}
153 	}
154 
155 	switch (imsg->hdr.type) {
156 	case IMSG_LPR_ALLOWEDHOST:
157 		m_get_string(proc, &hostname);
158 		m_get_string(proc, &reject);
159 		m_end(proc);
160 		lpr_on_allowedhost(conn, hostname, reject);
161 		break;
162 
163 	case IMSG_LPR_RECVJOB:
164 		m_get_int(proc, &ack);
165 		m_end(proc);
166 		lpr_on_recvjob(conn, ack);
167 		break;
168 
169 	case IMSG_LPR_RECVJOB_CF:
170 		cf = 1;
171 	case IMSG_LPR_RECVJOB_DF:
172 		m_get_int(proc, &ack);
173 		m_get_size(proc, &sz);
174 		m_end(proc);
175 		lpr_on_recvjob_file(conn, ack, sz, cf, imsg_get_fd(imsg));
176 		break;
177 
178 	case IMSG_LPR_DISPLAYQ:
179 	case IMSG_LPR_RMJOB:
180 		m_get_string(proc, &hostname);
181 		m_get_string(proc, &cmd);
182 		m_end(proc);
183 		lpr_on_request(conn, imsg_get_fd(imsg), hostname, cmd);
184 		break;
185 
186 	default:
187 		fatalx("%s: unexpected imsg %s", __func__,
188 		    log_fmt_imsgtype(imsg->hdr.type));
189 	}
190 }
191 
192 static void
lpr_on_allowedhost(struct lpr_conn * conn,const char * hostname,const char * reject)193 lpr_on_allowedhost(struct lpr_conn *conn, const char *hostname,
194     const char *reject)
195 {
196 	strlcpy(conn->hostname, hostname, sizeof(conn->hostname));
197 	if (reject)
198 		lpr_reply(conn, reject);
199 	else
200 		io_set_read(conn->io);
201 }
202 
203 static void
lpr_on_recvjob(struct lpr_conn * conn,int ack)204 lpr_on_recvjob(struct lpr_conn *conn, int ack)
205 {
206 	if (ack == LPR_ACK)
207 		conn->recvjob = 1;
208 	else
209 		log_debug("%08x recvjob failed", conn->id);
210 	lpr_ack(conn, ack);
211 }
212 
213 static void
lpr_on_recvjob_file(struct lpr_conn * conn,int ack,size_t sz,int cf,int fd)214 lpr_on_recvjob_file(struct lpr_conn *conn, int ack, size_t sz, int cf, int fd)
215 {
216 	if (ack != LPR_ACK) {
217 		lpr_ack(conn, ack);
218 		return;
219 	}
220 
221 	if (fd == -1) {
222 		log_warnx("%s: failed to get fd", __func__);
223 		lpr_ack(conn, LPR_NACK);
224 		return;
225 	}
226 
227 	conn->ofp = fdopen(fd, "w");
228 	if (conn->ofp == NULL) {
229 		log_warn("%s: fdopen", __func__);
230 		close(fd);
231 		lpr_ack(conn, LPR_NACK);
232 		return;
233 	}
234 
235 	conn->expect = sz;
236 	if (cf)
237 		conn->recvcf = cf;
238 	conn->state = STATE_READ_FILE;
239 
240 	lpr_ack(conn, LPR_ACK);
241 }
242 
243 static void
lpr_on_request(struct lpr_conn * conn,int fd,const char * hostname,const char * cmd)244 lpr_on_request(struct lpr_conn *conn, int fd, const char *hostname,
245     const char *cmd)
246 {
247 	struct addrinfo hints;
248 
249 	if (fd == -1) {
250 		log_warnx("%s: no fd received", __func__);
251 		lpr_close(conn);
252 		return;
253 	}
254 
255 	log_debug("%08x stream init", conn->id);
256 	conn->ifd = fd;
257 
258 	/* Prepare for command forwarding if necessary. */
259 	if (cmd) {
260 		log_debug("%08x forwarding to %s: \\%d%s", conn->id, hostname,
261 		    cmd[0], cmd + 1);
262 		conn->cmd = strdup(cmd);
263 		if (conn->cmd == NULL)
264 			log_warn("%s: strdup", __func__);
265 		else {
266 			memset(&hints, 0, sizeof(hints));
267 			hints.ai_socktype = SOCK_STREAM;
268 			conn->flags |= F_WAITADDRINFO;
269 			/*
270 			 * The callback might run immediately, so conn->ifd
271 			 * must be set before, to block lpr_forward().
272 			 */
273 			resolver_getaddrinfo(hostname, "printer", &hints,
274 			    lpr_on_getaddrinfo, conn);
275 		}
276 	}
277 
278 	lpr_stream(conn);
279 }
280 
281 static void
lpr_on_getaddrinfo(void * arg,int r,struct addrinfo * ai)282 lpr_on_getaddrinfo(void *arg, int r, struct addrinfo *ai)
283 {
284 	struct lpr_conn *conn = arg;
285 
286 	conn->flags &= ~F_WAITADDRINFO;
287 	if (conn->flags & F_ZOMBIE) {
288 		if (ai)
289 			freeaddrinfo(ai);
290 		lpr_free(conn);
291 	}
292 	else {
293 		conn->ai_done = 1;
294 		conn->ai = ai;
295 		lpr_forward(conn);
296 	}
297 }
298 
299 static void
lpr_io_dispatch(struct io * io,int evt,void * arg)300 lpr_io_dispatch(struct io *io, int evt, void *arg)
301 {
302 	struct lpr_conn *conn = arg;
303 	int r;
304 
305 	switch (evt) {
306 	case IO_DATAIN:
307 		switch(conn->state) {
308 		case STATE_READ_COMMAND:
309 			r = lpr_readcommand(conn);
310 			break;
311 		case STATE_READ_FILE:
312 			r = lpr_readfile(conn);
313 			break;
314 		default:
315 			fatal("%s: unexpected state %d", __func__, conn->state);
316 		}
317 
318 		if (r == 0)
319 			io_set_write(conn->io);
320 		return;
321 
322 	case IO_LOWAT:
323 		if (conn->recvjob)
324 			io_set_read(conn->io);
325 		else if (conn->ifd != -1)
326 			lpr_stream(conn);
327 		else if (conn->cmd == NULL)
328 			lpr_close(conn);
329 		return;
330 
331 	case IO_DISCONNECTED:
332 		log_debug("%08x disconnected", conn->id);
333 		/*
334 		 * Some clients don't wait for the last acknowledgment to close
335 		 * the session.  So just consider it is closed normally.
336 		 */
337 	case IO_CLOSED:
338 		if (conn->recvcf && conn->state == STATE_READ_COMMAND) {
339 			/*
340 			 * Commit the transaction if we received a control file
341 			 * and the last file was received correctly.
342 			 */
343 			m_compose(p_engine, IMSG_LPR_RECVJOB_COMMIT, conn->id,
344 			    0, -1, NULL, 0);
345 			conn->recvjob = 0;
346 		}
347 		break;
348 
349 	case IO_TIMEOUT:
350 		log_debug("%08x timeout", conn->id);
351 		break;
352 
353 	case IO_ERROR:
354 		log_debug("%08x io-error", conn->id);
355 		break;
356 
357 	default:
358 		fatalx("%s: unexpected event %d", __func__, evt);
359 	}
360 
361 	lpr_close(conn);
362 }
363 
364 static int
lpr_readcommand(struct lpr_conn * conn)365 lpr_readcommand(struct lpr_conn *conn)
366 {
367 	struct lp_jobfilter jf;
368 	size_t count;
369 	const char *errstr;
370 	char *argv[MAXARG], *line;
371 	int i, argc, cmd;
372 
373 	line = io_getline(conn->io, NULL);
374 	if (line == NULL) {
375 		if (io_datalen(conn->io) >= LPR_MAXCMDLEN) {
376 			lpr_reply(conn, "Request line too long");
377 			return 0;
378 		}
379 		return -1;
380 	}
381 
382 	cmd = line[0];
383 	line++;
384 
385 	if (cmd == 0) {
386 		lpr_reply(conn, "No command");
387 		return 0;
388 	}
389 
390 	log_debug("%08x cmd \\%d", conn->id, cmd);
391 
392 	/* Parse the command. */
393 	for (argc = 0; argc < MAXARG; ) {
394 		argv[argc] = strsep(&line, " \t");
395 		if (argv[argc] == NULL)
396 			break;
397 		if (argv[argc][0] != '\0')
398 			argc++;
399 	}
400 	if (argc == MAXARG) {
401 		lpr_reply(conn, "Argument list too long");
402 		return 0;
403 	}
404 
405 	if (argc == 0) {
406 		lpr_reply(conn, "No queue specified");
407 		return 0;
408 	}
409 
410 #define CMD(c)    ((int)(c))
411 #define SUBCMD(c) (0x100 | (int)(c))
412 
413 	if (conn->recvjob)
414 		cmd |= 0x100;
415 	switch (cmd) {
416 	case CMD('\1'):	/* PRINT <prn> */
417 		m_create(p_engine, IMSG_LPR_PRINTJOB, 0, 0, -1);
418 		m_add_string(p_engine, argv[0]);
419 		m_close(p_engine);
420 		lpr_ack(conn, LPR_ACK);
421 		return 0;
422 
423 	case CMD('\2'):	/* RECEIVE JOB <prn> */
424 		m_create(p_engine, IMSG_LPR_RECVJOB, conn->id, 0, -1);
425 		m_add_string(p_engine, conn->hostname);
426 		m_add_string(p_engine, argv[0]);
427 		m_close(p_engine);
428 		return 0;
429 
430 	case CMD('\3'):	/* QUEUE STATE SHORT <prn> [job#...] [user..] */
431 	case CMD('\4'):	/* QUEUE STATE LONG  <prn> [job#...] [user..] */
432 		if (lpr_parsejobfilter(conn, &jf, argc - 1, argv + 1) == -1)
433 			return 0;
434 
435 		m_create(p_engine, IMSG_LPR_DISPLAYQ, conn->id, 0, -1);
436 		m_add_int(p_engine, (cmd == '\3') ? 0 : 1);
437 		m_add_string(p_engine, conn->hostname);
438 		m_add_string(p_engine, argv[0]);
439 		m_add_int(p_engine, jf.njob);
440 		for (i = 0; i < jf.njob; i++)
441 			m_add_int(p_engine, jf.jobs[i]);
442 		m_add_int(p_engine, jf.nuser);
443 		for (i = 0; i < jf.nuser; i++)
444 			m_add_string(p_engine, jf.users[i]);
445 		m_close(p_engine);
446 		return 0;
447 
448 	case CMD('\5'):	/* REMOVE JOBS <prn> <agent> [job#...] [user..] */
449 		if (argc < 2) {
450 			lpr_reply(conn, "No agent specified");
451 			return 0;
452 		}
453 		if (lpr_parsejobfilter(conn, &jf, argc - 2, argv + 2) == -1)
454 			return 0;
455 
456 		m_create(p_engine, IMSG_LPR_RMJOB, conn->id, 0, -1);
457 		m_add_string(p_engine, conn->hostname);
458 		m_add_string(p_engine, argv[0]);
459 		m_add_string(p_engine, argv[1]);
460 		m_add_int(p_engine, jf.njob);
461 		for (i = 0; i < jf.njob; i++)
462 			m_add_int(p_engine, jf.jobs[i]);
463 		m_add_int(p_engine, jf.nuser);
464 		for (i = 0; i < jf.nuser; i++)
465 			m_add_string(p_engine, jf.users[i]);
466 		m_close(p_engine);
467 		return 0;
468 
469 	case SUBCMD('\1'):	/* ABORT */
470 		m_compose(p_engine, IMSG_LPR_RECVJOB_CLEAR, conn->id, 0, -1,
471 		    NULL, 0);
472 		conn->recvcf = 0;
473 		lpr_ack(conn, LPR_ACK);
474 		return 0;
475 
476 	case SUBCMD('\2'):	/* CONTROL FILE <size> <filename> */
477 	case SUBCMD('\3'):	/* DATA FILE    <size> <filename> */
478 		if (argc != 2) {
479 			log_debug("%08x invalid number of argument", conn->id);
480 			lpr_ack(conn, LPR_NACK);
481 			return 0;
482 		}
483 		errstr = NULL;
484 		count = strtonum(argv[0], 1, LPR_MAXFILESIZE, &errstr);
485 		if (errstr) {
486 			log_debug("%08x invalid file size: %s", conn->id,
487 			    strerror(errno));
488 			lpr_ack(conn, LPR_NACK);
489 			return 0;
490 		}
491 
492 		if (cmd == SUBCMD('\2')) {
493 			if (conn->recvcf) {
494 				log_debug("%08x cf file already received",
495 				    conn->id);
496 				lpr_ack(conn, LPR_NACK);
497 				return 0;
498 			}
499 			m_create(p_engine, IMSG_LPR_RECVJOB_CF, conn->id, 0,
500 			    -1);
501 		}
502 		else
503 			m_create(p_engine, IMSG_LPR_RECVJOB_DF, conn->id, 0,
504 			    -1);
505 		m_add_size(p_engine, count);
506 		m_add_string(p_engine, argv[1]);
507 		m_close(p_engine);
508 		return 0;
509 
510 	default:
511 		if (conn->recvjob)
512 			lpr_reply(conn, "Protocol error");
513 		else
514 			lpr_reply(conn, "Illegal service request");
515 		return 0;
516 	}
517 }
518 
519 static int
lpr_readfile(struct lpr_conn * conn)520 lpr_readfile(struct lpr_conn *conn)
521 {
522 	size_t len, w;
523 	char *data;
524 
525 	if (conn->expect) {
526 		/* Read file content. */
527 		data = io_data(conn->io);
528 		len = io_datalen(conn->io);
529 		if (len > conn->expect)
530 			len = conn->expect;
531 
532 		log_debug("%08x %zu bytes received", conn->id, len);
533 
534 		w = fwrite(data, 1, len, conn->ofp);
535 		if (w != len) {
536 			log_warnx("%s: fwrite", __func__);
537 			lpr_close(conn);
538 			return -1;
539 		}
540 		io_drop(conn->io, w);
541 		conn->expect -= w;
542 		if (conn->expect)
543 			return -1;
544 
545 		fclose(conn->ofp);
546 		conn->ofp = NULL;
547 
548 		log_debug("%08x file received", conn->id);
549 	}
550 
551 	/* Try to read '\0'. */
552 	len = io_datalen(conn->io);
553 	if (len == 0)
554 		return -1;
555 	data = io_data(conn->io);
556 	io_drop(conn->io, 1);
557 
558 	log_debug("%08x eof %d", conn->id, (int)*data);
559 
560 	if (*data != '\0') {
561 		lpr_close(conn);
562 		return -1;
563 	}
564 
565 	conn->state = STATE_READ_COMMAND;
566 	lpr_ack(conn, LPR_ACK);
567 	return 0;
568 }
569 
570 static int
lpr_parsejobfilter(struct lpr_conn * conn,struct lp_jobfilter * jf,int argc,char ** argv)571 lpr_parsejobfilter(struct lpr_conn *conn, struct lp_jobfilter *jf, int argc,
572     char **argv)
573 {
574 	const char *errstr;
575 	char *arg;
576 	int i, jobnum;
577 
578 	memset(jf, 0, sizeof(*jf));
579 
580 	for (i = 0; i < argc; i++) {
581 		arg = argv[i];
582 		if (isdigit((unsigned char)arg[0])) {
583 			if (jf->njob == LP_MAXREQUESTS) {
584 				lpr_reply(conn, "Too many requests");
585 				return -1;
586 			}
587 			errstr = NULL;
588 			jobnum = strtonum(arg, 0, INT_MAX, &errstr);
589 			if (errstr) {
590 				lpr_reply(conn, "Invalid job number");
591 				return -1;
592 			}
593 			jf->jobs[jf->njob++] = jobnum;
594 		}
595 		else {
596 			if (jf->nuser == LP_MAXUSERS) {
597 				lpr_reply(conn, "Too many users");
598 				return -1;
599 			}
600 			jf->users[jf->nuser++] = arg;
601 		}
602 	}
603 
604 	return 0;
605 }
606 
607 static void
lpr_free(struct lpr_conn * conn)608 lpr_free(struct lpr_conn *conn)
609 {
610 	if ((conn->flags & F_WAITADDRINFO) == 0)
611 		free(conn);
612 }
613 
614 static void
lpr_close(struct lpr_conn * conn)615 lpr_close(struct lpr_conn *conn)
616 {
617 	uint32_t connid = conn->id;
618 
619 	SPLAY_REMOVE(lpr_conn_tree, &conns, conn);
620 
621 	if (conn->recvjob)
622 		m_compose(p_engine, IMSG_LPR_RECVJOB_ROLLBACK, conn->id, 0, -1,
623 		    NULL, 0);
624 
625 	io_free(conn->io);
626 	free(conn->cmd);
627 	if (conn->ofp)
628 		fclose(conn->ofp);
629 	if (conn->ifd != -1)
630 		close(conn->ifd);
631 	if (conn->ai)
632 		freeaddrinfo(conn->ai);
633 	if (conn->iofwd)
634 		io_free(conn->iofwd);
635 
636 	conn->flags |= F_ZOMBIE;
637 	lpr_free(conn);
638 
639 	frontend_conn_closed(connid);
640 }
641 
642 static void
lpr_ack(struct lpr_conn * conn,char c)643 lpr_ack(struct lpr_conn *conn, char c)
644 {
645 	if (c == 0)
646 		log_debug("%08x ack", conn->id);
647 	else
648 		log_debug("%08x nack %d", conn->id, (int)c);
649 
650 	io_write(conn->io, &c, 1);
651 }
652 
653 static void
lpr_reply(struct lpr_conn * conn,const char * s)654 lpr_reply(struct lpr_conn *conn, const char *s)
655 {
656 	log_debug("%08x reply: %s", conn->id, s);
657 
658 	io_printf(conn->io, "%s\n", s);
659 }
660 
661 /*
662  * Stream response file to the client.
663  */
664 static void
lpr_stream(struct lpr_conn * conn)665 lpr_stream(struct lpr_conn *conn)
666 {
667 	char buf[BUFSIZ];
668 	ssize_t r;
669 
670 	for (;;) {
671 		if (io_queued(conn->io) > 65536)
672 			return;
673 
674 		r = read(conn->ifd, buf, sizeof(buf));
675 		if (r == -1) {
676 			if (errno == EINTR)
677 				continue;
678 			log_warn("%s: read", __func__);
679 			break;
680 		}
681 
682 		if (r == 0) {
683 			log_debug("%08x stream done", conn->id);
684 			break;
685 		}
686 		log_debug("%08x stream %zu bytes", conn->id, r);
687 
688 		if (io_write(conn->io, buf, r) == -1) {
689 			log_warn("%s: io_write", __func__);
690 			break;
691 		}
692 	}
693 
694 	close(conn->ifd);
695 	conn->ifd = -1;
696 
697 	if (conn->cmd)
698 		lpr_forward(conn);
699 
700 	else if (io_queued(conn->io) == 0)
701 		lpr_close(conn);
702 }
703 
704 /*
705  * Forward request to the remote printer.
706  */
707 static void
lpr_forward(struct lpr_conn * conn)708 lpr_forward(struct lpr_conn *conn)
709 {
710 	/*
711 	 * Do not start forwarding the command if the address is not resolved
712 	 * or if the local response is still being sent to the client.
713 	 */
714 	if (!conn->ai_done || conn->ifd == -1)
715 		return;
716 
717 	if (conn->ai == NULL) {
718 		if (io_queued(conn->io) == 0)
719 			lpr_close(conn);
720 		return;
721 	}
722 
723 	log_debug("%08x forward start", conn->id);
724 
725 	conn->iofwd = io_new();
726 	if (conn->iofwd == NULL) {
727 		log_warn("%s: io_new", __func__);
728 		if (io_queued(conn->io) == 0)
729 			lpr_close(conn);
730 		return;
731 	}
732 	io_set_callback(conn->iofwd, lpr_iofwd_dispatch, conn);
733 	io_set_timeout(conn->io, SERVER_TIMEOUT);
734 	io_connect(conn->iofwd, conn->ai);
735 	conn->ai = NULL;
736 }
737 
738 static void
lpr_iofwd_dispatch(struct io * io,int evt,void * arg)739 lpr_iofwd_dispatch(struct io *io, int evt, void *arg)
740 {
741 	struct lpr_conn *conn = arg;
742 
743 	switch (evt) {
744 	case IO_CONNECTED:
745 		log_debug("%08x forward connected", conn->id);
746 		/* Send the request. */
747 		io_print(io, conn->cmd);
748 		io_print(io, "\n");
749 		io_set_write(io);
750 		return;
751 
752 	case IO_DATAIN:
753 		/* Relay. */
754 		io_write(conn->io, io_data(io), io_datalen(io));
755 		io_drop(io, io_datalen(io));
756 		return;
757 
758 	case IO_LOWAT:
759 		/* Read response. */
760 		io_set_read(io);
761 		return;
762 
763 	case IO_CLOSED:
764 		break;
765 
766 	case IO_DISCONNECTED:
767 		log_debug("%08x forward disconnected", conn->id);
768 		break;
769 
770 	case IO_TIMEOUT:
771 		log_debug("%08x forward timeout", conn->id);
772 		break;
773 
774 	case IO_ERROR:
775 		log_debug("%08x forward io-error", conn->id);
776 		break;
777 
778 	default:
779 		fatalx("%s: unexpected event %d", __func__, evt);
780 	}
781 
782 	log_debug("%08x forward done", conn->id);
783 
784 	io_free(io);
785 	free(conn->cmd);
786 	conn->cmd = NULL;
787 	conn->iofwd = NULL;
788 	if (io_queued(conn->io) == 0)
789 		lpr_close(conn);
790 }
791 
792 static int
lpr_conn_cmp(struct lpr_conn * a,struct lpr_conn * b)793 lpr_conn_cmp(struct lpr_conn *a, struct lpr_conn *b)
794 {
795 	if (a->id < b->id)
796 		return -1;
797 	if (a->id > b->id)
798 		return 1;
799 	return 0;
800 }
801 
802 SPLAY_GENERATE(lpr_conn_tree, lpr_conn, entry, lpr_conn_cmp);
803