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 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 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 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 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 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 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 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 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 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 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 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 608 lpr_free(struct lpr_conn *conn) 609 { 610 if ((conn->flags & F_WAITADDRINFO) == 0) 611 free(conn); 612 } 613 614 static void 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 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 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 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 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 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 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