xref: /openbsd/usr.sbin/lpd/frontend_lpr.c (revision d415bd75)
1 /*	$OpenBSD: frontend_lpr.c,v 1.4 2022/12/28 21:30:17 jmc 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
99 lpr_init(void)
100 {
101 	SPLAY_INIT(&conns);
102 }
103 
104 void
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
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 			if (imsg->fd != -1)
152 				close(imsg->fd);
153 			return;
154 		}
155 	}
156 
157 	switch (imsg->hdr.type) {
158 	case IMSG_LPR_ALLOWEDHOST:
159 		m_get_string(proc, &hostname);
160 		m_get_string(proc, &reject);
161 		m_end(proc);
162 		lpr_on_allowedhost(conn, hostname, reject);
163 		break;
164 
165 	case IMSG_LPR_RECVJOB:
166 		m_get_int(proc, &ack);
167 		m_end(proc);
168 		lpr_on_recvjob(conn, ack);
169 		break;
170 
171 	case IMSG_LPR_RECVJOB_CF:
172 		cf = 1;
173 	case IMSG_LPR_RECVJOB_DF:
174 		m_get_int(proc, &ack);
175 		m_get_size(proc, &sz);
176 		m_end(proc);
177 		lpr_on_recvjob_file(conn, ack, sz, cf, imsg->fd);
178 		break;
179 
180 	case IMSG_LPR_DISPLAYQ:
181 	case IMSG_LPR_RMJOB:
182 		m_get_string(proc, &hostname);
183 		m_get_string(proc, &cmd);
184 		m_end(proc);
185 		lpr_on_request(conn, imsg->fd, hostname, cmd);
186 		break;
187 
188 	default:
189 		fatalx("%s: unexpected imsg %s", __func__,
190 		    log_fmt_imsgtype(imsg->hdr.type));
191 	}
192 }
193 
194 static void
195 lpr_on_allowedhost(struct lpr_conn *conn, const char *hostname,
196     const char *reject)
197 {
198 	strlcpy(conn->hostname, hostname, sizeof(conn->hostname));
199 	if (reject)
200 		lpr_reply(conn, reject);
201 	else
202 		io_set_read(conn->io);
203 }
204 
205 static void
206 lpr_on_recvjob(struct lpr_conn *conn, int ack)
207 {
208 	if (ack == LPR_ACK)
209 		conn->recvjob = 1;
210 	else
211 		log_debug("%08x recvjob failed", conn->id);
212 	lpr_ack(conn, ack);
213 }
214 
215 static void
216 lpr_on_recvjob_file(struct lpr_conn *conn, int ack, size_t sz, int cf, int fd)
217 {
218 	if (ack != LPR_ACK) {
219 		lpr_ack(conn, ack);
220 		return;
221 	}
222 
223 	if (fd == -1) {
224 		log_warnx("%s: failed to get fd", __func__);
225 		lpr_ack(conn, LPR_NACK);
226 		return;
227 	}
228 
229 	conn->ofp = fdopen(fd, "w");
230 	if (conn->ofp == NULL) {
231 		log_warn("%s: fdopen", __func__);
232 		close(fd);
233 		lpr_ack(conn, LPR_NACK);
234 		return;
235 	}
236 
237 	conn->expect = sz;
238 	if (cf)
239 		conn->recvcf = cf;
240 	conn->state = STATE_READ_FILE;
241 
242 	lpr_ack(conn, LPR_ACK);
243 }
244 
245 static void
246 lpr_on_request(struct lpr_conn *conn, int fd, const char *hostname,
247     const char *cmd)
248 {
249 	struct addrinfo hints;
250 
251 	if (fd == -1) {
252 		log_warnx("%s: no fd received", __func__);
253 		lpr_close(conn);
254 		return;
255 	}
256 
257 	log_debug("%08x stream init", conn->id);
258 	conn->ifd = fd;
259 
260 	/* Prepare for command forwarding if necessary. */
261 	if (cmd) {
262 		log_debug("%08x forwarding to %s: \\%d%s", conn->id, hostname,
263 		    cmd[0], cmd + 1);
264 		conn->cmd = strdup(cmd);
265 		if (conn->cmd == NULL)
266 			log_warn("%s: strdup", __func__);
267 		else {
268 			memset(&hints, 0, sizeof(hints));
269 			hints.ai_socktype = SOCK_STREAM;
270 			conn->flags |= F_WAITADDRINFO;
271 			/*
272 			 * The callback might run immediately, so conn->ifd
273 			 * must be set before, to block lpr_forward().
274 			 */
275 			resolver_getaddrinfo(hostname, "printer", &hints,
276 			    lpr_on_getaddrinfo, conn);
277 		}
278 	}
279 
280 	lpr_stream(conn);
281 }
282 
283 static void
284 lpr_on_getaddrinfo(void *arg, int r, struct addrinfo *ai)
285 {
286 	struct lpr_conn *conn = arg;
287 
288 	conn->flags &= ~F_WAITADDRINFO;
289 	if (conn->flags & F_ZOMBIE) {
290 		if (ai)
291 			freeaddrinfo(ai);
292 		lpr_free(conn);
293 	}
294 	else {
295 		conn->ai_done = 1;
296 		conn->ai = ai;
297 		lpr_forward(conn);
298 	}
299 }
300 
301 static void
302 lpr_io_dispatch(struct io *io, int evt, void *arg)
303 {
304 	struct lpr_conn *conn = arg;
305 	int r;
306 
307 	switch (evt) {
308 	case IO_DATAIN:
309 		switch(conn->state) {
310 		case STATE_READ_COMMAND:
311 			r = lpr_readcommand(conn);
312 			break;
313 		case STATE_READ_FILE:
314 			r = lpr_readfile(conn);
315 			break;
316 		default:
317 			fatal("%s: unexpected state %d", __func__, conn->state);
318 		}
319 
320 		if (r == 0)
321 			io_set_write(conn->io);
322 		return;
323 
324 	case IO_LOWAT:
325 		if (conn->recvjob)
326 			io_set_read(conn->io);
327 		else if (conn->ifd != -1)
328 			lpr_stream(conn);
329 		else if (conn->cmd == NULL)
330 			lpr_close(conn);
331 		return;
332 
333 	case IO_DISCONNECTED:
334 		log_debug("%08x disconnected", conn->id);
335 		/*
336 		 * Some clients don't wait for the last acknowledgment to close
337 		 * the session.  So just consider it is closed normally.
338 		 */
339 	case IO_CLOSED:
340 		if (conn->recvcf && conn->state == STATE_READ_COMMAND) {
341 			/*
342 			 * Commit the transaction if we received a control file
343 			 * and the last file was received correctly.
344 			 */
345 			m_compose(p_engine, IMSG_LPR_RECVJOB_COMMIT, conn->id,
346 			    0, -1, NULL, 0);
347 			conn->recvjob = 0;
348 		}
349 		break;
350 
351 	case IO_TIMEOUT:
352 		log_debug("%08x timeout", conn->id);
353 		break;
354 
355 	case IO_ERROR:
356 		log_debug("%08x io-error", conn->id);
357 		break;
358 
359 	default:
360 		fatalx("%s: unexpected event %d", __func__, evt);
361 	}
362 
363 	lpr_close(conn);
364 }
365 
366 static int
367 lpr_readcommand(struct lpr_conn *conn)
368 {
369 	struct lp_jobfilter jf;
370 	size_t count;
371 	const char *errstr;
372 	char *argv[MAXARG], *line;
373 	int i, argc, cmd;
374 
375 	line = io_getline(conn->io, NULL);
376 	if (line == NULL) {
377 		if (io_datalen(conn->io) >= LPR_MAXCMDLEN) {
378 			lpr_reply(conn, "Request line too long");
379 			return 0;
380 		}
381 		return -1;
382 	}
383 
384 	cmd = line[0];
385 	line++;
386 
387 	if (cmd == 0) {
388 		lpr_reply(conn, "No command");
389 		return 0;
390 	}
391 
392 	log_debug("%08x cmd \\%d", conn->id, cmd);
393 
394 	/* Parse the command. */
395 	for (argc = 0; argc < MAXARG; ) {
396 		argv[argc] = strsep(&line, " \t");
397 		if (argv[argc] == NULL)
398 			break;
399 		if (argv[argc][0] != '\0')
400 			argc++;
401 	}
402 	if (argc == MAXARG) {
403 		lpr_reply(conn, "Argument list too long");
404 		return 0;
405 	}
406 
407 	if (argc == 0) {
408 		lpr_reply(conn, "No queue specified");
409 		return 0;
410 	}
411 
412 #define CMD(c)    ((int)(c))
413 #define SUBCMD(c) (0x100 | (int)(c))
414 
415 	if (conn->recvjob)
416 		cmd |= 0x100;
417 	switch (cmd) {
418 	case CMD('\1'):	/* PRINT <prn> */
419 		m_create(p_engine, IMSG_LPR_PRINTJOB, 0, 0, -1);
420 		m_add_string(p_engine, argv[0]);
421 		m_close(p_engine);
422 		lpr_ack(conn, LPR_ACK);
423 		return 0;
424 
425 	case CMD('\2'):	/* RECEIVE JOB <prn> */
426 		m_create(p_engine, IMSG_LPR_RECVJOB, conn->id, 0, -1);
427 		m_add_string(p_engine, conn->hostname);
428 		m_add_string(p_engine, argv[0]);
429 		m_close(p_engine);
430 		return 0;
431 
432 	case CMD('\3'):	/* QUEUE STATE SHORT <prn> [job#...] [user..] */
433 	case CMD('\4'):	/* QUEUE STATE LONG  <prn> [job#...] [user..] */
434 		if (lpr_parsejobfilter(conn, &jf, argc - 1, argv + 1) == -1)
435 			return 0;
436 
437 		m_create(p_engine, IMSG_LPR_DISPLAYQ, conn->id, 0, -1);
438 		m_add_int(p_engine, (cmd == '\3') ? 0 : 1);
439 		m_add_string(p_engine, conn->hostname);
440 		m_add_string(p_engine, argv[0]);
441 		m_add_int(p_engine, jf.njob);
442 		for (i = 0; i < jf.njob; i++)
443 			m_add_int(p_engine, jf.jobs[i]);
444 		m_add_int(p_engine, jf.nuser);
445 		for (i = 0; i < jf.nuser; i++)
446 			m_add_string(p_engine, jf.users[i]);
447 		m_close(p_engine);
448 		return 0;
449 
450 	case CMD('\5'):	/* REMOVE JOBS <prn> <agent> [job#...] [user..] */
451 		if (argc < 2) {
452 			lpr_reply(conn, "No agent specified");
453 			return 0;
454 		}
455 		if (lpr_parsejobfilter(conn, &jf, argc - 2, argv + 2) == -1)
456 			return 0;
457 
458 		m_create(p_engine, IMSG_LPR_RMJOB, conn->id, 0, -1);
459 		m_add_string(p_engine, conn->hostname);
460 		m_add_string(p_engine, argv[0]);
461 		m_add_string(p_engine, argv[1]);
462 		m_add_int(p_engine, jf.njob);
463 		for (i = 0; i < jf.njob; i++)
464 			m_add_int(p_engine, jf.jobs[i]);
465 		m_add_int(p_engine, jf.nuser);
466 		for (i = 0; i < jf.nuser; i++)
467 			m_add_string(p_engine, jf.users[i]);
468 		m_close(p_engine);
469 		return 0;
470 
471 	case SUBCMD('\1'):	/* ABORT */
472 		m_compose(p_engine, IMSG_LPR_RECVJOB_CLEAR, conn->id, 0, -1,
473 		    NULL, 0);
474 		conn->recvcf = 0;
475 		lpr_ack(conn, LPR_ACK);
476 		return 0;
477 
478 	case SUBCMD('\2'):	/* CONTROL FILE <size> <filename> */
479 	case SUBCMD('\3'):	/* DATA FILE    <size> <filename> */
480 		if (argc != 2) {
481 			log_debug("%08x invalid number of argument", conn->id);
482 			lpr_ack(conn, LPR_NACK);
483 			return 0;
484 		}
485 		errstr = NULL;
486 		count = strtonum(argv[0], 1, LPR_MAXFILESIZE, &errstr);
487 		if (errstr) {
488 			log_debug("%08x invalid file size: %s", conn->id,
489 			    strerror(errno));
490 			lpr_ack(conn, LPR_NACK);
491 			return 0;
492 		}
493 
494 		if (cmd == SUBCMD('\2')) {
495 			if (conn->recvcf) {
496 				log_debug("%08x cf file already received",
497 				    conn->id);
498 				lpr_ack(conn, LPR_NACK);
499 				return 0;
500 			}
501 			m_create(p_engine, IMSG_LPR_RECVJOB_CF, conn->id, 0,
502 			    -1);
503 		}
504 		else
505 			m_create(p_engine, IMSG_LPR_RECVJOB_DF, conn->id, 0,
506 			    -1);
507 		m_add_size(p_engine, count);
508 		m_add_string(p_engine, argv[1]);
509 		m_close(p_engine);
510 		return 0;
511 
512 	default:
513 		if (conn->recvjob)
514 			lpr_reply(conn, "Protocol error");
515 		else
516 			lpr_reply(conn, "Illegal service request");
517 		return 0;
518 	}
519 }
520 
521 static int
522 lpr_readfile(struct lpr_conn *conn)
523 {
524 	size_t len, w;
525 	char *data;
526 
527 	if (conn->expect) {
528 		/* Read file content. */
529 		data = io_data(conn->io);
530 		len = io_datalen(conn->io);
531 		if (len > conn->expect)
532 			len = conn->expect;
533 
534 		log_debug("%08x %zu bytes received", conn->id, len);
535 
536 		w = fwrite(data, 1, len, conn->ofp);
537 		if (w != len) {
538 			log_warnx("%s: fwrite", __func__);
539 			lpr_close(conn);
540 			return -1;
541 		}
542 		io_drop(conn->io, w);
543 		conn->expect -= w;
544 		if (conn->expect)
545 			return -1;
546 
547 		fclose(conn->ofp);
548 		conn->ofp = NULL;
549 
550 		log_debug("%08x file received", conn->id);
551 	}
552 
553 	/* Try to read '\0'. */
554 	len = io_datalen(conn->io);
555 	if (len == 0)
556 		return -1;
557 	data = io_data(conn->io);
558 	io_drop(conn->io, 1);
559 
560 	log_debug("%08x eof %d", conn->id, (int)*data);
561 
562 	if (*data != '\0') {
563 		lpr_close(conn);
564 		return -1;
565 	}
566 
567 	conn->state = STATE_READ_COMMAND;
568 	lpr_ack(conn, LPR_ACK);
569 	return 0;
570 }
571 
572 static int
573 lpr_parsejobfilter(struct lpr_conn *conn, struct lp_jobfilter *jf, int argc,
574     char **argv)
575 {
576 	const char *errstr;
577 	char *arg;
578 	int i, jobnum;
579 
580 	memset(jf, 0, sizeof(*jf));
581 
582 	for (i = 0; i < argc; i++) {
583 		arg = argv[i];
584 		if (isdigit((unsigned char)arg[0])) {
585 			if (jf->njob == LP_MAXREQUESTS) {
586 				lpr_reply(conn, "Too many requests");
587 				return -1;
588 			}
589 			errstr = NULL;
590 			jobnum = strtonum(arg, 0, INT_MAX, &errstr);
591 			if (errstr) {
592 				lpr_reply(conn, "Invalid job number");
593 				return -1;
594 			}
595 			jf->jobs[jf->njob++] = jobnum;
596 		}
597 		else {
598 			if (jf->nuser == LP_MAXUSERS) {
599 				lpr_reply(conn, "Too many users");
600 				return -1;
601 			}
602 			jf->users[jf->nuser++] = arg;
603 		}
604 	}
605 
606 	return 0;
607 }
608 
609 static void
610 lpr_free(struct lpr_conn *conn)
611 {
612 	if ((conn->flags & F_WAITADDRINFO) == 0)
613 		free(conn);
614 }
615 
616 static void
617 lpr_close(struct lpr_conn *conn)
618 {
619 	uint32_t connid = conn->id;
620 
621 	SPLAY_REMOVE(lpr_conn_tree, &conns, conn);
622 
623 	if (conn->recvjob)
624 		m_compose(p_engine, IMSG_LPR_RECVJOB_ROLLBACK, conn->id, 0, -1,
625 		    NULL, 0);
626 
627 	io_free(conn->io);
628 	free(conn->cmd);
629 	if (conn->ofp)
630 		fclose(conn->ofp);
631 	if (conn->ifd != -1)
632 		close(conn->ifd);
633 	if (conn->ai)
634 		freeaddrinfo(conn->ai);
635 	if (conn->iofwd)
636 		io_free(conn->iofwd);
637 
638 	conn->flags |= F_ZOMBIE;
639 	lpr_free(conn);
640 
641 	frontend_conn_closed(connid);
642 }
643 
644 static void
645 lpr_ack(struct lpr_conn *conn, char c)
646 {
647 	if (c == 0)
648 		log_debug("%08x ack", conn->id);
649 	else
650 		log_debug("%08x nack %d", conn->id, (int)c);
651 
652 	io_write(conn->io, &c, 1);
653 }
654 
655 static void
656 lpr_reply(struct lpr_conn *conn, const char *s)
657 {
658 	log_debug("%08x reply: %s", conn->id, s);
659 
660 	io_printf(conn->io, "%s\n", s);
661 }
662 
663 /*
664  * Stream response file to the client.
665  */
666 static void
667 lpr_stream(struct lpr_conn *conn)
668 {
669 	char buf[BUFSIZ];
670 	ssize_t r;
671 
672 	for (;;) {
673 		if (io_queued(conn->io) > 65536)
674 			return;
675 
676 		r = read(conn->ifd, buf, sizeof(buf));
677 		if (r == -1) {
678 			if (errno == EINTR)
679 				continue;
680 			log_warn("%s: read", __func__);
681 			break;
682 		}
683 
684 		if (r == 0) {
685 			log_debug("%08x stream done", conn->id);
686 			break;
687 		}
688 		log_debug("%08x stream %zu bytes", conn->id, r);
689 
690 		if (io_write(conn->io, buf, r) == -1) {
691 			log_warn("%s: io_write", __func__);
692 			break;
693 		}
694 	}
695 
696 	close(conn->ifd);
697 	conn->ifd = -1;
698 
699 	if (conn->cmd)
700 		lpr_forward(conn);
701 
702 	else if (io_queued(conn->io) == 0)
703 		lpr_close(conn);
704 }
705 
706 /*
707  * Forward request to the remote printer.
708  */
709 static void
710 lpr_forward(struct lpr_conn *conn)
711 {
712 	/*
713 	 * Do not start forwarding the command if the address is not resolved
714 	 * or if the local response is still being sent to the client.
715 	 */
716 	if (!conn->ai_done || conn->ifd == -1)
717 		return;
718 
719 	if (conn->ai == NULL) {
720 		if (io_queued(conn->io) == 0)
721 			lpr_close(conn);
722 		return;
723 	}
724 
725 	log_debug("%08x forward start", conn->id);
726 
727 	conn->iofwd = io_new();
728 	if (conn->iofwd == NULL) {
729 		log_warn("%s: io_new", __func__);
730 		if (io_queued(conn->io) == 0)
731 			lpr_close(conn);
732 		return;
733 	}
734 	io_set_callback(conn->iofwd, lpr_iofwd_dispatch, conn);
735 	io_set_timeout(conn->io, SERVER_TIMEOUT);
736 	io_connect(conn->iofwd, conn->ai);
737 	conn->ai = NULL;
738 }
739 
740 static void
741 lpr_iofwd_dispatch(struct io *io, int evt, void *arg)
742 {
743 	struct lpr_conn *conn = arg;
744 
745 	switch (evt) {
746 	case IO_CONNECTED:
747 		log_debug("%08x forward connected", conn->id);
748 		/* Send the request. */
749 		io_print(io, conn->cmd);
750 		io_print(io, "\n");
751 		io_set_write(io);
752 		return;
753 
754 	case IO_DATAIN:
755 		/* Relay. */
756 		io_write(conn->io, io_data(io), io_datalen(io));
757 		io_drop(io, io_datalen(io));
758 		return;
759 
760 	case IO_LOWAT:
761 		/* Read response. */
762 		io_set_read(io);
763 		return;
764 
765 	case IO_CLOSED:
766 		break;
767 
768 	case IO_DISCONNECTED:
769 		log_debug("%08x forward disconnected", conn->id);
770 		break;
771 
772 	case IO_TIMEOUT:
773 		log_debug("%08x forward timeout", conn->id);
774 		break;
775 
776 	case IO_ERROR:
777 		log_debug("%08x forward io-error", conn->id);
778 		break;
779 
780 	default:
781 		fatalx("%s: unexpected event %d", __func__, evt);
782 	}
783 
784 	log_debug("%08x forward done", conn->id);
785 
786 	io_free(io);
787 	free(conn->cmd);
788 	conn->cmd = NULL;
789 	conn->iofwd = NULL;
790 	if (io_queued(conn->io) == 0)
791 		lpr_close(conn);
792 }
793 
794 static int
795 lpr_conn_cmp(struct lpr_conn *a, struct lpr_conn *b)
796 {
797 	if (a->id < b->id)
798 		return -1;
799 	if (a->id > b->id)
800 		return 1;
801 	return 0;
802 }
803 
804 SPLAY_GENERATE(lpr_conn_tree, lpr_conn, entry, lpr_conn_cmp);
805