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