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