1/* 2 * "$Id: cups-lpd.c 11645 2014-02-27 16:35:53Z msweet $" 3 * 4 * Line Printer Daemon interface for CUPS. 5 * 6 * Copyright 2007-2014 by Apple Inc. 7 * Copyright 1997-2006 by Easy Software Products, all rights reserved. 8 * 9 * These coded instructions, statements, and computer programs are the 10 * property of Apple Inc. and are protected by Federal copyright 11 * law. Distribution and use rights are outlined in the file "LICENSE.txt" 12 * which should have been included with this file. If this file is 13 * file is missing or damaged, see the license at "http://www.cups.org/". 14 */ 15 16/* 17 * Include necessary headers... 18 */ 19 20#include <cups/cups-private.h> 21#include <syslog.h> 22#include <unistd.h> 23#include <fcntl.h> 24#include <sys/types.h> 25#include <sys/socket.h> 26#include <netinet/in.h> 27#include <netdb.h> 28 29#ifdef HAVE_INTTYPES_H 30# include <inttypes.h> 31#endif /* HAVE_INTTYPES_H */ 32 33 34/* 35 * LPD "mini-daemon" for CUPS. This program must be used in conjunction 36 * with inetd or another similar program that monitors ports and starts 37 * daemons for each client connection. A typical configuration is: 38 * 39 * printer stream tcp nowait lp /usr/lib/cups/daemon/cups-lpd cups-lpd 40 * 41 * This daemon implements most of RFC 1179 (the unofficial LPD specification) 42 * except for: 43 * 44 * - This daemon does not check to make sure that the source port is 45 * between 721 and 731, since it isn't necessary for proper 46 * functioning and port-based security is no security at all! 47 * 48 * - The "Print any waiting jobs" command is a no-op. 49 * 50 * The LPD-to-IPP mapping is as defined in RFC 2569. The report formats 51 * currently match the Solaris LPD mini-daemon. 52 */ 53 54/* 55 * Prototypes... 56 */ 57 58static int create_job(http_t *http, const char *dest, const char *title, 59 const char *docname, const char *user, 60 int num_options, cups_option_t *options); 61static int get_printer(http_t *http, const char *name, char *dest, 62 size_t destsize, cups_option_t **options, 63 int *accepting, int *shared, ipp_pstate_t *state); 64static int print_file(http_t *http, int id, const char *filename, 65 const char *docname, const char *user, 66 const char *format, int last); 67static int recv_print_job(const char *name, int num_defaults, 68 cups_option_t *defaults); 69static int remove_jobs(const char *name, const char *agent, 70 const char *list); 71static int send_state(const char *name, const char *list, 72 int longstatus); 73static char *smart_gets(char *s, int len, FILE *fp); 74 75 76/* 77 * 'main()' - Process an incoming LPD request... 78 */ 79 80int /* O - Exit status */ 81main(int argc, /* I - Number of command-line arguments */ 82 char *argv[]) /* I - Command-line arguments */ 83{ 84 int i; /* Looping var */ 85 int num_defaults; /* Number of default options */ 86 cups_option_t *defaults; /* Default options */ 87 char line[256], /* Command string */ 88 command, /* Command code */ 89 *dest, /* Pointer to destination */ 90 *list, /* Pointer to list */ 91 *agent, /* Pointer to user */ 92 status; /* Status for client */ 93 socklen_t hostlen; /* Size of client address */ 94 http_addr_t hostaddr; /* Address of client */ 95 char hostname[256], /* Name of client */ 96 hostip[256], /* IP address */ 97 *hostfamily; /* Address family */ 98 int hostlookups; /* Do hostname lookups? */ 99 100 101 /* 102 * Don't buffer the output... 103 */ 104 105 setbuf(stdout, NULL); 106 107 /* 108 * Log things using the "cups-lpd" name... 109 */ 110 111 openlog("cups-lpd", LOG_PID, LOG_LPR); 112 113 /* 114 * Scan the command-line for options... 115 */ 116 117 num_defaults = 0; 118 defaults = NULL; 119 hostlookups = 1; 120 121 for (i = 1; i < argc; i ++) 122 if (argv[i][0] == '-') 123 { 124 switch (argv[i][1]) 125 { 126 case 'h' : /* -h hostname[:port] */ 127 if (argv[i][2]) 128 cupsSetServer(argv[i] + 2); 129 else 130 { 131 i ++; 132 if (i < argc) 133 cupsSetServer(argv[i]); 134 else 135 syslog(LOG_WARNING, "Expected hostname string after -h option!"); 136 } 137 break; 138 139 case 'o' : /* Option */ 140 if (argv[i][2]) 141 num_defaults = cupsParseOptions(argv[i] + 2, num_defaults, 142 &defaults); 143 else 144 { 145 i ++; 146 if (i < argc) 147 num_defaults = cupsParseOptions(argv[i], num_defaults, 148 &defaults); 149 else 150 syslog(LOG_WARNING, "Expected option string after -o option!"); 151 } 152 break; 153 154 case 'n' : /* Don't do hostname lookups */ 155 hostlookups = 0; 156 break; 157 158 default : 159 syslog(LOG_WARNING, "Unknown option \"%c\" ignored!", argv[i][1]); 160 break; 161 } 162 } 163 else 164 syslog(LOG_WARNING, "Unknown command-line option \"%s\" ignored!", 165 argv[i]); 166 167 /* 168 * Get the address of the client... 169 */ 170 171 hostlen = sizeof(hostaddr); 172 173 if (getpeername(0, (struct sockaddr *)&hostaddr, &hostlen)) 174 { 175 syslog(LOG_WARNING, "Unable to get client address - %s", strerror(errno)); 176 strlcpy(hostname, "unknown", sizeof(hostname)); 177 } 178 else 179 { 180 httpAddrString(&hostaddr, hostip, sizeof(hostip)); 181 182 if (hostlookups) 183 httpAddrLookup(&hostaddr, hostname, sizeof(hostname)); 184 else 185 strlcpy(hostname, hostip, sizeof(hostname)); 186 187#ifdef AF_INET6 188 if (hostaddr.addr.sa_family == AF_INET6) 189 hostfamily = "IPv6"; 190 else 191#endif /* AF_INET6 */ 192 hostfamily = "IPv4"; 193 194 syslog(LOG_INFO, "Connection from %s (%s %s)", hostname, hostfamily, 195 hostip); 196 } 197 198 num_defaults = cupsAddOption("job-originating-host-name", hostname, 199 num_defaults, &defaults); 200 201 /* 202 * RFC1179 specifies that only 1 daemon command can be received for 203 * every connection. 204 */ 205 206 if (smart_gets(line, sizeof(line), stdin) == NULL) 207 { 208 /* 209 * Unable to get command from client! Send an error status and return. 210 */ 211 212 syslog(LOG_ERR, "Unable to get command line from client!"); 213 putchar(1); 214 return (1); 215 } 216 217 /* 218 * The first byte is the command byte. After that will be the queue name, 219 * resource list, and/or user name. 220 */ 221 222 command = line[0]; 223 dest = line + 1; 224 225 if (command == 0x02) 226 list = NULL; 227 else 228 { 229 for (list = dest; *list && !isspace(*list & 255); list ++); 230 231 while (isspace(*list & 255)) 232 *list++ = '\0'; 233 } 234 235 /* 236 * Do the command... 237 */ 238 239 switch (command) 240 { 241 default : /* Unknown command */ 242 syslog(LOG_ERR, "Unknown LPD command 0x%02X!", command); 243 syslog(LOG_ERR, "Command line = %s", line + 1); 244 putchar(1); 245 246 status = 1; 247 break; 248 249 case 0x01 : /* Print any waiting jobs */ 250 syslog(LOG_INFO, "Print waiting jobs (no-op)"); 251 putchar(0); 252 253 status = 0; 254 break; 255 256 case 0x02 : /* Receive a printer job */ 257 syslog(LOG_INFO, "Receive print job for %s", dest); 258 /* recv_print_job() sends initial status byte */ 259 260 status = (char)recv_print_job(dest, num_defaults, defaults); 261 break; 262 263 case 0x03 : /* Send queue state (short) */ 264 syslog(LOG_INFO, "Send queue state (short) for %s %s", dest, list); 265 /* no status byte for this command */ 266 267 status = (char)send_state(dest, list, 0); 268 break; 269 270 case 0x04 : /* Send queue state (long) */ 271 syslog(LOG_INFO, "Send queue state (long) for %s %s", dest, list); 272 /* no status byte for this command */ 273 274 status = (char)send_state(dest, list, 1); 275 break; 276 277 case 0x05 : /* Remove jobs */ 278 if (list) 279 { 280 /* 281 * Grab the agent and skip to the list of users and/or jobs. 282 */ 283 284 agent = list; 285 286 for (; *list && !isspace(*list & 255); list ++); 287 while (isspace(*list & 255)) 288 *list++ = '\0'; 289 290 syslog(LOG_INFO, "Remove jobs %s on %s by %s", list, dest, agent); 291 292 status = (char)remove_jobs(dest, agent, list); 293 } 294 else 295 status = 1; 296 297 putchar(status); 298 break; 299 } 300 301 syslog(LOG_INFO, "Closing connection"); 302 closelog(); 303 304 return (status); 305} 306 307 308/* 309 * 'create_job()' - Create a new print job. 310 */ 311 312static int /* O - Job ID or -1 on error */ 313create_job(http_t *http, /* I - HTTP connection */ 314 const char *dest, /* I - Destination name */ 315 const char *title, /* I - job-name */ 316 const char *docname, /* I - Name of job file */ 317 const char *user, /* I - requesting-user-name */ 318 int num_options, /* I - Number of options for job */ 319 cups_option_t *options) /* I - Options for job */ 320{ 321 ipp_t *request; /* IPP request */ 322 ipp_t *response; /* IPP response */ 323 ipp_attribute_t *attr; /* IPP attribute */ 324 char uri[HTTP_MAX_URI]; /* Printer URI */ 325 int id; /* Job ID */ 326 327 328 /* 329 * Setup the Create-Job request... 330 */ 331 332 request = ippNewRequest(IPP_CREATE_JOB); 333 334 httpAssembleURIf(HTTP_URI_CODING_ALL, uri, sizeof(uri), "ipp", NULL, 335 "localhost", 0, "/printers/%s", dest); 336 337 ippAddString(request, IPP_TAG_OPERATION, IPP_TAG_URI, "printer-uri", 338 NULL, uri); 339 340 ippAddString(request, IPP_TAG_OPERATION, IPP_TAG_NAME, 341 "requesting-user-name", NULL, user); 342 343 if (title[0]) 344 ippAddString(request, IPP_TAG_OPERATION, IPP_TAG_NAME, "job-name", 345 NULL, title); 346 347 if (docname[0]) 348 ippAddString(request, IPP_TAG_OPERATION, IPP_TAG_NAME, "document-name", 349 NULL, docname); 350 351 cupsEncodeOptions(request, num_options, options); 352 353 /* 354 * Do the request... 355 */ 356 357 snprintf(uri, sizeof(uri), "/printers/%s", dest); 358 359 response = cupsDoRequest(http, request, uri); 360 361 if (!response || cupsLastError() > IPP_OK_CONFLICT) 362 { 363 syslog(LOG_ERR, "Unable to create job - %s", cupsLastErrorString()); 364 365 ippDelete(response); 366 367 return (-1); 368 } 369 370 /* 371 * Get the job-id value from the response and return it... 372 */ 373 374 if ((attr = ippFindAttribute(response, "job-id", IPP_TAG_INTEGER)) == NULL) 375 { 376 id = -1; 377 378 syslog(LOG_ERR, "No job-id attribute found in response from server!"); 379 } 380 else 381 { 382 id = attr->values[0].integer; 383 384 syslog(LOG_INFO, "Print file - job ID = %d", id); 385 } 386 387 ippDelete(response); 388 389 return (id); 390} 391 392 393/* 394 * 'get_printer()' - Get the named printer and its options. 395 */ 396 397static int /* O - Number of options or -1 on error */ 398get_printer(http_t *http, /* I - HTTP connection */ 399 const char *name, /* I - Printer name from request */ 400 char *dest, /* I - Destination buffer */ 401 size_t destsize, /* I - Size of destination buffer */ 402 cups_option_t **options, /* O - Printer options */ 403 int *accepting, /* O - printer-is-accepting-jobs value */ 404 int *shared, /* O - printer-is-shared value */ 405 ipp_pstate_t *state) /* O - printer-state value */ 406{ 407 int num_options; /* Number of options */ 408 cups_file_t *fp; /* lpoptions file */ 409 char line[1024], /* Line from lpoptions file */ 410 *value, /* Pointer to value on line */ 411 *optptr; /* Pointer to options on line */ 412 int linenum; /* Line number in file */ 413 const char *cups_serverroot; /* CUPS_SERVERROOT env var */ 414 ipp_t *request; /* IPP request */ 415 ipp_t *response; /* IPP response */ 416 ipp_attribute_t *attr; /* IPP attribute */ 417 char uri[HTTP_MAX_URI]; /* Printer URI */ 418 static const char * const requested[] = 419 { /* Requested attributes */ 420 "printer-info", 421 "printer-is-accepting-jobs", 422 "printer-is-shared", 423 "printer-name", 424 "printer-state" 425 }; 426 427 428 /* 429 * Initialize everything... 430 */ 431 432 if (accepting) 433 *accepting = 0; 434 if (shared) 435 *shared = 0; 436 if (state) 437 *state = IPP_PRINTER_STOPPED; 438 if (options) 439 *options = NULL; 440 441 /* 442 * See if the name is a queue name optionally with an instance name. 443 */ 444 445 strlcpy(dest, name, destsize); 446 if ((value = strchr(dest, '/')) != NULL) 447 *value = '\0'; 448 449 /* 450 * Setup the Get-Printer-Attributes request... 451 */ 452 453 request = ippNewRequest(IPP_GET_PRINTER_ATTRIBUTES); 454 455 httpAssembleURIf(HTTP_URI_CODING_ALL, uri, sizeof(uri), "ipp", NULL, 456 "localhost", 0, "/printers/%s", dest); 457 458 ippAddString(request, IPP_TAG_OPERATION, IPP_TAG_URI, "printer-uri", 459 NULL, uri); 460 461 ippAddStrings(request, IPP_TAG_OPERATION, IPP_TAG_KEYWORD, 462 "requested-attributes", 463 (int)(sizeof(requested) / sizeof(requested[0])), 464 NULL, requested); 465 466 /* 467 * Do the request... 468 */ 469 470 response = cupsDoRequest(http, request, "/"); 471 472 if (!response || cupsLastError() > IPP_OK_CONFLICT) 473 { 474 /* 475 * If we can't find the printer by name, look up the printer-name 476 * using the printer-info values... 477 */ 478 479 ipp_attribute_t *accepting_attr,/* printer-is-accepting-jobs */ 480 *info_attr, /* printer-info */ 481 *name_attr, /* printer-name */ 482 *shared_attr, /* printer-is-shared */ 483 *state_attr; /* printer-state */ 484 485 486 ippDelete(response); 487 488 /* 489 * Setup the CUPS-Get-Printers request... 490 */ 491 492 request = ippNewRequest(CUPS_GET_PRINTERS); 493 494 ippAddStrings(request, IPP_TAG_OPERATION, IPP_TAG_KEYWORD, 495 "requested-attributes", 496 (int)(sizeof(requested) / sizeof(requested[0])), 497 NULL, requested); 498 499 /* 500 * Do the request... 501 */ 502 503 response = cupsDoRequest(http, request, "/"); 504 505 if (!response || cupsLastError() > IPP_OK_CONFLICT) 506 { 507 syslog(LOG_ERR, "Unable to get list of printers - %s", 508 cupsLastErrorString()); 509 510 ippDelete(response); 511 512 return (-1); 513 } 514 515 /* 516 * Scan the response for printers... 517 */ 518 519 *dest = '\0'; 520 attr = response->attrs; 521 522 while (attr) 523 { 524 /* 525 * Skip to the next printer... 526 */ 527 528 while (attr && attr->group_tag != IPP_TAG_PRINTER) 529 attr = attr->next; 530 531 if (!attr) 532 break; 533 534 /* 535 * Get all of the attributes for the current printer... 536 */ 537 538 accepting_attr = NULL; 539 info_attr = NULL; 540 name_attr = NULL; 541 shared_attr = NULL; 542 state_attr = NULL; 543 544 while (attr && attr->group_tag == IPP_TAG_PRINTER) 545 { 546 if (!strcmp(attr->name, "printer-is-accepting-jobs") && 547 attr->value_tag == IPP_TAG_BOOLEAN) 548 accepting_attr = attr; 549 else if (!strcmp(attr->name, "printer-info") && 550 attr->value_tag == IPP_TAG_TEXT) 551 info_attr = attr; 552 else if (!strcmp(attr->name, "printer-name") && 553 attr->value_tag == IPP_TAG_NAME) 554 name_attr = attr; 555 else if (!strcmp(attr->name, "printer-is-shared") && 556 attr->value_tag == IPP_TAG_BOOLEAN) 557 shared_attr = attr; 558 else if (!strcmp(attr->name, "printer-state") && 559 attr->value_tag == IPP_TAG_ENUM) 560 state_attr = attr; 561 562 attr = attr->next; 563 } 564 565 if (info_attr && name_attr && 566 !_cups_strcasecmp(name, info_attr->values[0].string.text)) 567 { 568 /* 569 * Found a match, use this one! 570 */ 571 572 strlcpy(dest, name_attr->values[0].string.text, destsize); 573 574 if (accepting && accepting_attr) 575 *accepting = accepting_attr->values[0].boolean; 576 577 if (shared && shared_attr) 578 *shared = shared_attr->values[0].boolean; 579 580 if (state && state_attr) 581 *state = (ipp_pstate_t)state_attr->values[0].integer; 582 583 break; 584 } 585 } 586 587 ippDelete(response); 588 589 if (!*dest) 590 { 591 syslog(LOG_ERR, "Unable to find \"%s\" in list of printers!", name); 592 593 return (-1); 594 } 595 596 name = dest; 597 } 598 else 599 { 600 /* 601 * Get values from the response... 602 */ 603 604 if (accepting) 605 { 606 if ((attr = ippFindAttribute(response, "printer-is-accepting-jobs", 607 IPP_TAG_BOOLEAN)) == NULL) 608 syslog(LOG_ERR, "No printer-is-accepting-jobs attribute found in " 609 "response from server!"); 610 else 611 *accepting = attr->values[0].boolean; 612 } 613 614 if (shared) 615 { 616 if ((attr = ippFindAttribute(response, "printer-is-shared", 617 IPP_TAG_BOOLEAN)) == NULL) 618 { 619 syslog(LOG_ERR, "No printer-is-shared attribute found in " 620 "response from server!"); 621 *shared = 1; 622 } 623 else 624 *shared = attr->values[0].boolean; 625 } 626 627 if (state) 628 { 629 if ((attr = ippFindAttribute(response, "printer-state", 630 IPP_TAG_ENUM)) == NULL) 631 syslog(LOG_ERR, "No printer-state attribute found in " 632 "response from server!"); 633 else 634 *state = (ipp_pstate_t)attr->values[0].integer; 635 } 636 637 ippDelete(response); 638 } 639 640 /* 641 * Next look for the printer in the lpoptions file... 642 */ 643 644 num_options = 0; 645 646 if (options && shared && accepting) 647 { 648 if ((cups_serverroot = getenv("CUPS_SERVERROOT")) == NULL) 649 cups_serverroot = CUPS_SERVERROOT; 650 651 snprintf(line, sizeof(line), "%s/lpoptions", cups_serverroot); 652 if ((fp = cupsFileOpen(line, "r")) != NULL) 653 { 654 linenum = 0; 655 while (cupsFileGetConf(fp, line, sizeof(line), &value, &linenum)) 656 { 657 /* 658 * Make sure we have "Dest name options" or "Default name options"... 659 */ 660 661 if ((_cups_strcasecmp(line, "Dest") && _cups_strcasecmp(line, "Default")) || !value) 662 continue; 663 664 /* 665 * Separate destination name from options... 666 */ 667 668 for (optptr = value; *optptr && !isspace(*optptr & 255); optptr ++); 669 670 while (*optptr == ' ') 671 *optptr++ = '\0'; 672 673 /* 674 * If this is our destination, parse the options and break out of 675 * the loop - we're done! 676 */ 677 678 if (!_cups_strcasecmp(value, name)) 679 { 680 num_options = cupsParseOptions(optptr, num_options, options); 681 break; 682 } 683 } 684 685 cupsFileClose(fp); 686 } 687 } 688 else if (options) 689 *options = NULL; 690 691 /* 692 * Return the number of options for this destination... 693 */ 694 695 return (num_options); 696} 697 698 699/* 700 * 'print_file()' - Add a file to the current job. 701 */ 702 703static int /* O - 0 on success, -1 on failure */ 704print_file(http_t *http, /* I - HTTP connection */ 705 int id, /* I - Job ID */ 706 const char *filename, /* I - File to print */ 707 const char *docname, /* I - document-name */ 708 const char *user, /* I - requesting-user-name */ 709 const char *format, /* I - document-format */ 710 int last) /* I - 1 = last file in job */ 711{ 712 ipp_t *request; /* IPP request */ 713 char uri[HTTP_MAX_URI]; /* Printer URI */ 714 715 716 /* 717 * Setup the Send-Document request... 718 */ 719 720 request = ippNewRequest(IPP_SEND_DOCUMENT); 721 722 snprintf(uri, sizeof(uri), "ipp://localhost/jobs/%d", id); 723 ippAddString(request, IPP_TAG_OPERATION, IPP_TAG_URI, "job-uri", NULL, uri); 724 725 ippAddString(request, IPP_TAG_OPERATION, IPP_TAG_NAME, 726 "requesting-user-name", NULL, user); 727 728 if (docname) 729 ippAddString(request, IPP_TAG_OPERATION, IPP_TAG_NAME, 730 "document-name", NULL, docname); 731 732 if (format) 733 ippAddString(request, IPP_TAG_OPERATION, IPP_TAG_MIMETYPE, 734 "document-format", NULL, format); 735 736 ippAddBoolean(request, IPP_TAG_OPERATION, "last-document", (char)last); 737 738 /* 739 * Do the request... 740 */ 741 742 snprintf(uri, sizeof(uri), "/jobs/%d", id); 743 744 ippDelete(cupsDoFileRequest(http, request, uri, filename)); 745 746 if (cupsLastError() > IPP_OK_CONFLICT) 747 { 748 syslog(LOG_ERR, "Unable to send document - %s", cupsLastErrorString()); 749 750 return (-1); 751 } 752 753 return (0); 754} 755 756 757/* 758 * 'recv_print_job()' - Receive a print job from the client. 759 */ 760 761static int /* O - Command status */ 762recv_print_job( 763 const char *queue, /* I - Printer name */ 764 int num_defaults, /* I - Number of default options */ 765 cups_option_t *defaults) /* I - Default options */ 766{ 767 http_t *http; /* HTTP connection */ 768 int i; /* Looping var */ 769 int status; /* Command status */ 770 int fd; /* Temporary file */ 771 FILE *fp; /* File pointer */ 772 char filename[1024]; /* Temporary filename */ 773 ssize_t bytes; /* Bytes received */ 774 size_t total; /* Total bytes */ 775 char line[256], /* Line from file/stdin */ 776 command, /* Command from line */ 777 *count, /* Number of bytes */ 778 *name; /* Name of file */ 779 const char *job_sheets; /* Job sheets */ 780 int num_data; /* Number of data files */ 781 char control[1024], /* Control filename */ 782 data[100][256], /* Data files */ 783 temp[100][1024]; /* Temporary files */ 784 char user[1024], /* User name */ 785 title[1024], /* Job title */ 786 docname[1024], /* Document name */ 787 dest[256]; /* Printer/class queue */ 788 int accepting, /* printer-is-accepting */ 789 shared, /* printer-is-shared */ 790 num_options; /* Number of options */ 791 cups_option_t *options; /* Options */ 792 int id; /* Job ID */ 793 int docnumber, /* Current document number */ 794 doccount; /* Count of documents */ 795 796 797 /* 798 * Connect to the server... 799 */ 800 801 http = httpConnectEncrypt(cupsServer(), ippPort(), cupsEncryption()); 802 if (!http) 803 { 804 syslog(LOG_ERR, "Unable to connect to server: %s", strerror(errno)); 805 806 putchar(1); 807 808 return (1); 809 } 810 811 /* 812 * See if the printer is available... 813 */ 814 815 num_options = get_printer(http, queue, dest, sizeof(dest), &options, 816 &accepting, &shared, NULL); 817 818 if (num_options < 0 || !accepting || !shared) 819 { 820 if (dest[0]) 821 syslog(LOG_INFO, "Rejecting job because \"%s\" is not %s", dest, 822 !accepting ? "accepting jobs" : "shared"); 823 else 824 syslog(LOG_ERR, "Unable to get printer information for \"%s\"", queue); 825 826 httpClose(http); 827 828 putchar(1); 829 830 return (1); 831 } 832 833 putchar(0); /* OK so far... */ 834 835 /* 836 * Read the request... 837 */ 838 839 status = 0; 840 num_data = 0; 841 fd = -1; 842 843 control[0] = '\0'; 844 845 while (smart_gets(line, sizeof(line), stdin) != NULL) 846 { 847 if (strlen(line) < 2) 848 { 849 status = 1; 850 break; 851 } 852 853 command = line[0]; 854 count = line + 1; 855 856 for (name = count + 1; *name && !isspace(*name & 255); name ++); 857 while (isspace(*name & 255)) 858 *name++ = '\0'; 859 860 switch (command) 861 { 862 default : 863 case 0x01 : /* Abort */ 864 status = 1; 865 break; 866 867 case 0x02 : /* Receive control file */ 868 if (strlen(name) < 2) 869 { 870 syslog(LOG_ERR, "Bad control file name \"%s\"", name); 871 putchar(1); 872 status = 1; 873 break; 874 } 875 876 if (control[0]) 877 { 878 /* 879 * Append to the existing control file - the LPD spec is 880 * not entirely clear, but at least the OS/2 LPD code sends 881 * multiple control files per connection... 882 */ 883 884 if ((fd = open(control, O_WRONLY)) < 0) 885 { 886 syslog(LOG_ERR, 887 "Unable to append to temporary control file \"%s\" - %s", 888 control, strerror(errno)); 889 putchar(1); 890 status = 1; 891 break; 892 } 893 894 lseek(fd, 0, SEEK_END); 895 } 896 else 897 { 898 if ((fd = cupsTempFd(control, sizeof(control))) < 0) 899 { 900 syslog(LOG_ERR, "Unable to open temporary control file \"%s\" - %s", 901 control, strerror(errno)); 902 putchar(1); 903 status = 1; 904 break; 905 } 906 907 strlcpy(filename, control, sizeof(filename)); 908 } 909 break; 910 911 case 0x03 : /* Receive data file */ 912 if (strlen(name) < 2) 913 { 914 syslog(LOG_ERR, "Bad data file name \"%s\"", name); 915 putchar(1); 916 status = 1; 917 break; 918 } 919 920 if (num_data >= (int)(sizeof(data) / sizeof(data[0]))) 921 { 922 /* 923 * Too many data files... 924 */ 925 926 syslog(LOG_ERR, "Too many data files (%d)", num_data); 927 putchar(1); 928 status = 1; 929 break; 930 } 931 932 strlcpy(data[num_data], name, sizeof(data[0])); 933 934 if ((fd = cupsTempFd(temp[num_data], sizeof(temp[0]))) < 0) 935 { 936 syslog(LOG_ERR, "Unable to open temporary data file \"%s\" - %s", 937 temp[num_data], strerror(errno)); 938 putchar(1); 939 status = 1; 940 break; 941 } 942 943 strlcpy(filename, temp[num_data], sizeof(filename)); 944 945 num_data ++; 946 break; 947 } 948 949 putchar(status); 950 951 if (status) 952 break; 953 954 /* 955 * Copy the data or control file from the client... 956 */ 957 958 for (total = (size_t)strtoll(count, NULL, 10); total > 0; total -= (size_t)bytes) 959 { 960 if (total > sizeof(line)) 961 bytes = (ssize_t)sizeof(line); 962 else 963 bytes = (ssize_t)total; 964 965 if ((bytes = (ssize_t)fread(line, 1, (size_t)bytes, stdin)) > 0) 966 bytes = write(fd, line, (size_t)bytes); 967 968 if (bytes < 1) 969 { 970 syslog(LOG_ERR, "Error while reading file - %s", 971 strerror(errno)); 972 status = 1; 973 break; 974 } 975 } 976 977 /* 978 * Read trailing nul... 979 */ 980 981 if (!status) 982 { 983 if (fread(line, 1, 1, stdin) < 1) 984 { 985 status = 1; 986 syslog(LOG_ERR, "Error while reading trailing nul - %s", 987 strerror(errno)); 988 } 989 else if (line[0]) 990 { 991 status = 1; 992 syslog(LOG_ERR, "Trailing character after file is not nul (%02X)!", 993 line[0]); 994 } 995 } 996 997 /* 998 * Close the file and send an acknowledgement... 999 */ 1000 1001 close(fd); 1002 1003 putchar(status); 1004 1005 if (status) 1006 break; 1007 } 1008 1009 if (!status) 1010 { 1011 /* 1012 * Process the control file and print stuff... 1013 */ 1014 1015 if ((fp = fopen(control, "rb")) == NULL) 1016 status = 1; 1017 else 1018 { 1019 /* 1020 * Copy the default options... 1021 */ 1022 1023 for (i = 0; i < num_defaults; i ++) 1024 num_options = cupsAddOption(defaults[i].name, 1025 defaults[i].value, 1026 num_options, &options); 1027 1028 /* 1029 * Grab the job information... 1030 */ 1031 1032 title[0] = '\0'; 1033 user[0] = '\0'; 1034 docname[0] = '\0'; 1035 doccount = 0; 1036 1037 while (smart_gets(line, sizeof(line), fp) != NULL) 1038 { 1039 /* 1040 * Process control lines... 1041 */ 1042 1043 switch (line[0]) 1044 { 1045 case 'J' : /* Job name */ 1046 strlcpy(title, line + 1, sizeof(title)); 1047 break; 1048 1049 case 'N' : /* Document name */ 1050 strlcpy(docname, line + 1, sizeof(docname)); 1051 break; 1052 1053 case 'P' : /* User identification */ 1054 strlcpy(user, line + 1, sizeof(user)); 1055 break; 1056 1057 case 'L' : /* Print banner page */ 1058 /* 1059 * If a banner was requested and it's not overridden by a 1060 * command line option and the destination's default is none 1061 * then add the standard banner... 1062 */ 1063 1064 if (cupsGetOption("job-sheets", num_defaults, defaults) == NULL && 1065 ((job_sheets = cupsGetOption("job-sheets", num_options, 1066 options)) == NULL || 1067 !strcmp(job_sheets, "none,none"))) 1068 { 1069 num_options = cupsAddOption("job-sheets", "standard", 1070 num_options, &options); 1071 } 1072 break; 1073 1074 case 'c' : /* Plot CIF file */ 1075 case 'd' : /* Print DVI file */ 1076 case 'f' : /* Print formatted file */ 1077 case 'g' : /* Plot file */ 1078 case 'l' : /* Print file leaving control characters (raw) */ 1079 case 'n' : /* Print ditroff output file */ 1080 case 'o' : /* Print PostScript output file */ 1081 case 'p' : /* Print file with 'pr' format (prettyprint) */ 1082 case 'r' : /* File to print with FORTRAN carriage control */ 1083 case 't' : /* Print troff output file */ 1084 case 'v' : /* Print raster file */ 1085 doccount ++; 1086 1087 if (line[0] == 'l' && 1088 !cupsGetOption("document-format", num_options, options)) 1089 num_options = cupsAddOption("raw", "", num_options, &options); 1090 1091 if (line[0] == 'p') 1092 num_options = cupsAddOption("prettyprint", "", num_options, 1093 &options); 1094 break; 1095 } 1096 1097 if (status) 1098 break; 1099 } 1100 1101 /* 1102 * Check that we have a username... 1103 */ 1104 1105 if (!user[0]) 1106 { 1107 syslog(LOG_WARNING, "No username specified by client! " 1108 "Using \"anonymous\"..."); 1109 strlcpy(user, "anonymous", sizeof(user)); 1110 } 1111 1112 /* 1113 * Create the job... 1114 */ 1115 1116 if ((id = create_job(http, dest, title, docname, user, num_options, 1117 options)) < 0) 1118 status = 1; 1119 else 1120 { 1121 /* 1122 * Then print the job files... 1123 */ 1124 1125 rewind(fp); 1126 1127 docname[0] = '\0'; 1128 docnumber = 0; 1129 1130 while (smart_gets(line, sizeof(line), fp) != NULL) 1131 { 1132 /* 1133 * Process control lines... 1134 */ 1135 1136 switch (line[0]) 1137 { 1138 case 'N' : /* Document name */ 1139 strlcpy(docname, line + 1, sizeof(docname)); 1140 break; 1141 1142 case 'c' : /* Plot CIF file */ 1143 case 'd' : /* Print DVI file */ 1144 case 'f' : /* Print formatted file */ 1145 case 'g' : /* Plot file */ 1146 case 'l' : /* Print file leaving control characters (raw) */ 1147 case 'n' : /* Print ditroff output file */ 1148 case 'o' : /* Print PostScript output file */ 1149 case 'p' : /* Print file with 'pr' format (prettyprint) */ 1150 case 'r' : /* File to print with FORTRAN carriage control */ 1151 case 't' : /* Print troff output file */ 1152 case 'v' : /* Print raster file */ 1153 /* 1154 * Figure out which file we are printing... 1155 */ 1156 1157 for (i = 0; i < num_data; i ++) 1158 if (!strcmp(data[i], line + 1)) 1159 break; 1160 1161 if (i >= num_data) 1162 { 1163 status = 1; 1164 break; 1165 } 1166 1167 /* 1168 * Send the print file... 1169 */ 1170 1171 docnumber ++; 1172 1173 if (print_file(http, id, temp[i], docname, user, 1174 cupsGetOption("document-format", num_options, 1175 options), 1176 docnumber == doccount)) 1177 status = 1; 1178 else 1179 status = 0; 1180 1181 break; 1182 } 1183 1184 if (status) 1185 break; 1186 } 1187 } 1188 1189 fclose(fp); 1190 } 1191 } 1192 1193 cupsFreeOptions(num_options, options); 1194 1195 httpClose(http); 1196 1197 /* 1198 * Clean up all temporary files and return... 1199 */ 1200 1201 unlink(control); 1202 1203 for (i = 0; i < num_data; i ++) 1204 unlink(temp[i]); 1205 1206 return (status); 1207} 1208 1209 1210/* 1211 * 'remove_jobs()' - Cancel one or more jobs. 1212 */ 1213 1214static int /* O - Command status */ 1215remove_jobs(const char *dest, /* I - Destination */ 1216 const char *agent, /* I - User agent */ 1217 const char *list) /* I - List of jobs or users */ 1218{ 1219 int id; /* Job ID */ 1220 http_t *http; /* HTTP server connection */ 1221 ipp_t *request; /* IPP Request */ 1222 char uri[HTTP_MAX_URI]; /* Job URI */ 1223 1224 1225 (void)dest; /* Suppress compiler warnings... */ 1226 1227 /* 1228 * Try connecting to the local server... 1229 */ 1230 1231 if ((http = httpConnectEncrypt(cupsServer(), ippPort(), 1232 cupsEncryption())) == NULL) 1233 { 1234 syslog(LOG_ERR, "Unable to connect to server %s: %s", cupsServer(), 1235 strerror(errno)); 1236 return (1); 1237 } 1238 1239 /* 1240 * Loop for each job... 1241 */ 1242 1243 while ((id = atoi(list)) > 0) 1244 { 1245 /* 1246 * Skip job ID in list... 1247 */ 1248 1249 while (isdigit(*list & 255)) 1250 list ++; 1251 while (isspace(*list & 255)) 1252 list ++; 1253 1254 /* 1255 * Build an IPP_CANCEL_JOB request, which requires the following 1256 * attributes: 1257 * 1258 * attributes-charset 1259 * attributes-natural-language 1260 * job-uri 1261 * requesting-user-name 1262 */ 1263 1264 request = ippNewRequest(IPP_CANCEL_JOB); 1265 1266 sprintf(uri, "ipp://localhost/jobs/%d", id); 1267 ippAddString(request, IPP_TAG_OPERATION, IPP_TAG_URI, "job-uri", NULL, uri); 1268 1269 ippAddString(request, IPP_TAG_OPERATION, IPP_TAG_NAME, 1270 "requesting-user-name", NULL, agent); 1271 1272 /* 1273 * Do the request and get back a response... 1274 */ 1275 1276 ippDelete(cupsDoRequest(http, request, "/jobs")); 1277 1278 if (cupsLastError() > IPP_OK_CONFLICT) 1279 { 1280 syslog(LOG_WARNING, "Cancel of job ID %d failed: %s\n", id, 1281 cupsLastErrorString()); 1282 httpClose(http); 1283 return (1); 1284 } 1285 else 1286 syslog(LOG_INFO, "Job ID %d canceled", id); 1287 } 1288 1289 httpClose(http); 1290 1291 return (0); 1292} 1293 1294 1295/* 1296 * 'send_state()' - Send the queue state. 1297 */ 1298 1299static int /* O - Command status */ 1300send_state(const char *queue, /* I - Destination */ 1301 const char *list, /* I - Job or user */ 1302 int longstatus) /* I - List of jobs or users */ 1303{ 1304 int id; /* Job ID from list */ 1305 http_t *http; /* HTTP server connection */ 1306 ipp_t *request, /* IPP Request */ 1307 *response; /* IPP Response */ 1308 ipp_attribute_t *attr; /* Current attribute */ 1309 ipp_pstate_t state; /* Printer state */ 1310 const char *jobdest, /* Pointer into job-printer-uri */ 1311 *jobuser, /* Pointer to job-originating-user-name */ 1312 *jobname; /* Pointer to job-name */ 1313 ipp_jstate_t jobstate; /* job-state */ 1314 int jobid, /* job-id */ 1315 jobsize, /* job-k-octets */ 1316 jobcount, /* Number of jobs */ 1317 jobcopies, /* Number of copies */ 1318 rank; /* Rank of job */ 1319 char rankstr[255]; /* Rank string */ 1320 char namestr[1024]; /* Job name string */ 1321 char uri[HTTP_MAX_URI]; /* Printer URI */ 1322 char dest[256]; /* Printer/class queue */ 1323 static const char * const ranks[10] = /* Ranking strings */ 1324 { 1325 "th", 1326 "st", 1327 "nd", 1328 "rd", 1329 "th", 1330 "th", 1331 "th", 1332 "th", 1333 "th", 1334 "th" 1335 }; 1336 static const char * const requested[] = 1337 { /* Requested attributes */ 1338 "job-id", 1339 "job-k-octets", 1340 "job-state", 1341 "job-printer-uri", 1342 "job-originating-user-name", 1343 "job-name", 1344 "copies" 1345 }; 1346 1347 1348 /* 1349 * Try connecting to the local server... 1350 */ 1351 1352 if ((http = httpConnectEncrypt(cupsServer(), ippPort(), 1353 cupsEncryption())) == NULL) 1354 { 1355 syslog(LOG_ERR, "Unable to connect to server %s: %s", cupsServer(), 1356 strerror(errno)); 1357 printf("Unable to connect to server %s: %s", cupsServer(), strerror(errno)); 1358 return (1); 1359 } 1360 1361 /* 1362 * Get the actual destination name and printer state... 1363 */ 1364 1365 if (get_printer(http, queue, dest, sizeof(dest), NULL, NULL, NULL, &state)) 1366 { 1367 syslog(LOG_ERR, "Unable to get printer %s: %s", queue, 1368 cupsLastErrorString()); 1369 printf("Unable to get printer %s: %s", queue, cupsLastErrorString()); 1370 return (1); 1371 } 1372 1373 /* 1374 * Show the queue state... 1375 */ 1376 1377 switch (state) 1378 { 1379 case IPP_PRINTER_IDLE : 1380 printf("%s is ready\n", dest); 1381 break; 1382 case IPP_PRINTER_PROCESSING : 1383 printf("%s is ready and printing\n", dest); 1384 break; 1385 case IPP_PRINTER_STOPPED : 1386 printf("%s is not ready\n", dest); 1387 break; 1388 } 1389 1390 /* 1391 * Build an IPP_GET_JOBS or IPP_GET_JOB_ATTRIBUTES request, which requires 1392 * the following attributes: 1393 * 1394 * attributes-charset 1395 * attributes-natural-language 1396 * job-uri or printer-uri 1397 */ 1398 1399 id = atoi(list); 1400 1401 request = ippNewRequest(id ? IPP_GET_JOB_ATTRIBUTES : IPP_GET_JOBS); 1402 1403 httpAssembleURIf(HTTP_URI_CODING_ALL, uri, sizeof(uri), "ipp", NULL, 1404 "localhost", 0, "/printers/%s", dest); 1405 1406 ippAddString(request, IPP_TAG_OPERATION, IPP_TAG_URI, "printer-uri", 1407 NULL, uri); 1408 1409 if (id) 1410 ippAddInteger(request, IPP_TAG_OPERATION, IPP_TAG_INTEGER, "job-id", id); 1411 else 1412 { 1413 ippAddString(request, IPP_TAG_OPERATION, IPP_TAG_NAME, 1414 "requesting-user-name", NULL, list); 1415 ippAddBoolean(request, IPP_TAG_OPERATION, "my-jobs", 1); 1416 } 1417 1418 ippAddStrings(request, IPP_TAG_OPERATION, IPP_TAG_KEYWORD, 1419 "requested-attributes", 1420 sizeof(requested) / sizeof(requested[0]), 1421 NULL, requested); 1422 1423 /* 1424 * Do the request and get back a response... 1425 */ 1426 1427 jobcount = 0; 1428 response = cupsDoRequest(http, request, "/"); 1429 1430 if (cupsLastError() > IPP_OK_CONFLICT) 1431 { 1432 printf("get-jobs failed: %s\n", cupsLastErrorString()); 1433 ippDelete(response); 1434 return (1); 1435 } 1436 1437 /* 1438 * Loop through the job list and display them... 1439 */ 1440 1441 for (attr = response->attrs, rank = 1; attr; attr = attr->next) 1442 { 1443 /* 1444 * Skip leading attributes until we hit a job... 1445 */ 1446 1447 while (attr && (attr->group_tag != IPP_TAG_JOB || !attr->name)) 1448 attr = attr->next; 1449 1450 if (!attr) 1451 break; 1452 1453 /* 1454 * Pull the needed attributes from this job... 1455 */ 1456 1457 jobid = 0; 1458 jobsize = 0; 1459 jobstate = IPP_JOB_PENDING; 1460 jobname = "untitled"; 1461 jobuser = NULL; 1462 jobdest = NULL; 1463 jobcopies = 1; 1464 1465 while (attr && attr->group_tag == IPP_TAG_JOB) 1466 { 1467 if (!strcmp(attr->name, "job-id") && 1468 attr->value_tag == IPP_TAG_INTEGER) 1469 jobid = attr->values[0].integer; 1470 1471 if (!strcmp(attr->name, "job-k-octets") && 1472 attr->value_tag == IPP_TAG_INTEGER) 1473 jobsize = attr->values[0].integer; 1474 1475 if (!strcmp(attr->name, "job-state") && 1476 attr->value_tag == IPP_TAG_ENUM) 1477 jobstate = (ipp_jstate_t)attr->values[0].integer; 1478 1479 if (!strcmp(attr->name, "job-printer-uri") && 1480 attr->value_tag == IPP_TAG_URI) 1481 if ((jobdest = strrchr(attr->values[0].string.text, '/')) != NULL) 1482 jobdest ++; 1483 1484 if (!strcmp(attr->name, "job-originating-user-name") && 1485 attr->value_tag == IPP_TAG_NAME) 1486 jobuser = attr->values[0].string.text; 1487 1488 if (!strcmp(attr->name, "job-name") && 1489 attr->value_tag == IPP_TAG_NAME) 1490 jobname = attr->values[0].string.text; 1491 1492 if (!strcmp(attr->name, "copies") && 1493 attr->value_tag == IPP_TAG_INTEGER) 1494 jobcopies = attr->values[0].integer; 1495 1496 attr = attr->next; 1497 } 1498 1499 /* 1500 * See if we have everything needed... 1501 */ 1502 1503 if (!jobdest || !jobid) 1504 { 1505 if (!attr) 1506 break; 1507 else 1508 continue; 1509 } 1510 1511 if (!longstatus && jobcount == 0) 1512 puts("Rank Owner Job File(s) Total Size"); 1513 1514 jobcount ++; 1515 1516 /* 1517 * Display the job... 1518 */ 1519 1520 if (jobstate == IPP_JOB_PROCESSING) 1521 strlcpy(rankstr, "active", sizeof(rankstr)); 1522 else 1523 { 1524 snprintf(rankstr, sizeof(rankstr), "%d%s", rank, ranks[rank % 10]); 1525 rank ++; 1526 } 1527 1528 if (longstatus) 1529 { 1530 puts(""); 1531 1532 if (jobcopies > 1) 1533 snprintf(namestr, sizeof(namestr), "%d copies of %s", jobcopies, 1534 jobname); 1535 else 1536 strlcpy(namestr, jobname, sizeof(namestr)); 1537 1538 printf("%s: %-33.33s [job %d localhost]\n", jobuser, rankstr, jobid); 1539 printf(" %-39.39s %.0f bytes\n", namestr, 1024.0 * jobsize); 1540 } 1541 else 1542 printf("%-7s %-7.7s %-7d %-31.31s %.0f bytes\n", rankstr, jobuser, 1543 jobid, jobname, 1024.0 * jobsize); 1544 1545 if (!attr) 1546 break; 1547 } 1548 1549 ippDelete(response); 1550 1551 if (jobcount == 0) 1552 puts("no entries"); 1553 1554 httpClose(http); 1555 1556 return (0); 1557} 1558 1559 1560/* 1561 * 'smart_gets()' - Get a line of text, removing the trailing CR and/or LF. 1562 */ 1563 1564static char * /* O - Line read or NULL */ 1565smart_gets(char *s, /* I - Pointer to line buffer */ 1566 int len, /* I - Size of line buffer */ 1567 FILE *fp) /* I - File to read from */ 1568{ 1569 char *ptr, /* Pointer into line */ 1570 *end; /* End of line */ 1571 int ch; /* Character from file */ 1572 1573 1574 /* 1575 * Read the line; unlike fgets(), we read the entire line but dump 1576 * characters that go past the end of the buffer. Also, we accept 1577 * CR, LF, or CR LF for the line endings to be "safe", although 1578 * RFC 1179 specifically says "just use LF". 1579 */ 1580 1581 ptr = s; 1582 end = s + len - 1; 1583 1584 while ((ch = getc(fp)) != EOF) 1585 { 1586 if (ch == '\n') 1587 break; 1588 else if (ch == '\r') 1589 { 1590 /* 1591 * See if a LF follows... 1592 */ 1593 1594 ch = getc(fp); 1595 1596 if (ch != '\n') 1597 ungetc(ch, fp); 1598 1599 break; 1600 } 1601 else if (ptr < end) 1602 *ptr++ = (char)ch; 1603 } 1604 1605 *ptr = '\0'; 1606 1607 if (ch == EOF && ptr == s) 1608 return (NULL); 1609 else 1610 return (s); 1611} 1612 1613 1614/* 1615 * End of "$Id: cups-lpd.c 11645 2014-02-27 16:35:53Z msweet $". 1616 */ 1617