Haibo Huang | 265f757 | 2019-12-13 16:47:52 -0800 | [diff] [blame^] | 1 | /* |
| 2 | * AppSocket backend for CUPS. |
| 3 | * |
| 4 | * Copyright © 2007-2018 by Apple Inc. |
| 5 | * Copyright © 1997-2007 by Easy Software Products, all rights reserved. |
| 6 | * |
| 7 | * Licensed under Apache License v2.0. See the file "LICENSE" for more |
| 8 | * information. |
| 9 | */ |
| 10 | |
| 11 | /* |
| 12 | * Include necessary headers. |
| 13 | */ |
| 14 | |
| 15 | #include <cups/http-private.h> |
| 16 | #include "backend-private.h" |
| 17 | #include <stdarg.h> |
| 18 | #include <sys/types.h> |
| 19 | #include <sys/stat.h> |
| 20 | |
| 21 | #ifdef _WIN32 |
| 22 | # include <winsock.h> |
| 23 | #else |
| 24 | # include <unistd.h> |
| 25 | # include <fcntl.h> |
| 26 | # include <sys/socket.h> |
| 27 | # include <netinet/in.h> |
| 28 | # include <arpa/inet.h> |
| 29 | # include <netdb.h> |
| 30 | #endif /* _WIN32 */ |
| 31 | |
| 32 | |
| 33 | /* |
| 34 | * Local functions... |
| 35 | */ |
| 36 | |
| 37 | static ssize_t wait_bc(int device_fd, int secs); |
| 38 | |
| 39 | |
| 40 | /* |
| 41 | * 'main()' - Send a file to the printer or server. |
| 42 | * |
| 43 | * Usage: |
| 44 | * |
| 45 | * printer-uri job-id user title copies options [file] |
| 46 | */ |
| 47 | |
| 48 | int /* O - Exit status */ |
| 49 | main(int argc, /* I - Number of command-line arguments (6 or 7) */ |
| 50 | char *argv[]) /* I - Command-line arguments */ |
| 51 | { |
| 52 | const char *device_uri; /* Device URI */ |
| 53 | char scheme[255], /* Scheme in URI */ |
| 54 | hostname[1024], /* Hostname */ |
| 55 | username[255], /* Username info (not used) */ |
| 56 | resource[1024], /* Resource info (not used) */ |
| 57 | *options, /* Pointer to options */ |
| 58 | *name, /* Name of option */ |
| 59 | *value, /* Value of option */ |
| 60 | sep; /* Option separator */ |
| 61 | int print_fd; /* Print file */ |
| 62 | int copies; /* Number of copies to print */ |
| 63 | time_t start_time; /* Time of first connect */ |
| 64 | int contimeout; /* Connection timeout */ |
| 65 | int waiteof; /* Wait for end-of-file? */ |
| 66 | int port; /* Port number */ |
| 67 | int delay; /* Delay for retries... */ |
| 68 | int device_fd; /* AppSocket */ |
| 69 | int error; /* Error code (if any) */ |
| 70 | http_addrlist_t *addrlist, /* Address list */ |
| 71 | *addr; /* Connected address */ |
| 72 | char addrname[256]; /* Address name */ |
| 73 | int snmp_enabled = 1; /* Is SNMP enabled? */ |
| 74 | int snmp_fd, /* SNMP socket */ |
| 75 | start_count, /* Page count via SNMP at start */ |
| 76 | page_count, /* Page count via SNMP */ |
| 77 | have_supplies; /* Printer supports supply levels? */ |
| 78 | ssize_t bytes = 0, /* Initial bytes read */ |
| 79 | tbytes; /* Total number of bytes written */ |
| 80 | char buffer[1024]; /* Initial print buffer */ |
| 81 | #if defined(HAVE_SIGACTION) && !defined(HAVE_SIGSET) |
| 82 | struct sigaction action; /* Actions for POSIX signals */ |
| 83 | #endif /* HAVE_SIGACTION && !HAVE_SIGSET */ |
| 84 | |
| 85 | |
| 86 | /* |
| 87 | * Make sure status messages are not buffered... |
| 88 | */ |
| 89 | |
| 90 | setbuf(stderr, NULL); |
| 91 | |
| 92 | /* |
| 93 | * Ignore SIGPIPE signals... |
| 94 | */ |
| 95 | |
| 96 | #ifdef HAVE_SIGSET |
| 97 | sigset(SIGPIPE, SIG_IGN); |
| 98 | #elif defined(HAVE_SIGACTION) |
| 99 | memset(&action, 0, sizeof(action)); |
| 100 | action.sa_handler = SIG_IGN; |
| 101 | sigaction(SIGPIPE, &action, NULL); |
| 102 | #else |
| 103 | signal(SIGPIPE, SIG_IGN); |
| 104 | #endif /* HAVE_SIGSET */ |
| 105 | |
| 106 | /* |
| 107 | * Check command-line... |
| 108 | */ |
| 109 | |
| 110 | if (argc == 1) |
| 111 | { |
| 112 | printf("network socket \"Unknown\" \"%s\"\n", |
| 113 | _cupsLangString(cupsLangDefault(), _("AppSocket/HP JetDirect"))); |
| 114 | return (CUPS_BACKEND_OK); |
| 115 | } |
| 116 | else if (argc < 6 || argc > 7) |
| 117 | { |
| 118 | _cupsLangPrintf(stderr, |
| 119 | _("Usage: %s job-id user title copies options [file]"), |
| 120 | argv[0]); |
| 121 | return (CUPS_BACKEND_FAILED); |
| 122 | } |
| 123 | |
| 124 | /* |
| 125 | * If we have 7 arguments, print the file named on the command-line. |
| 126 | * Otherwise, send stdin instead... |
| 127 | */ |
| 128 | |
| 129 | if (argc == 6) |
| 130 | { |
| 131 | print_fd = 0; |
| 132 | copies = 1; |
| 133 | } |
| 134 | else |
| 135 | { |
| 136 | /* |
| 137 | * Try to open the print file... |
| 138 | */ |
| 139 | |
| 140 | if ((print_fd = open(argv[6], O_RDONLY)) < 0) |
| 141 | { |
| 142 | _cupsLangPrintError("ERROR", _("Unable to open print file")); |
| 143 | return (CUPS_BACKEND_FAILED); |
| 144 | } |
| 145 | |
| 146 | copies = atoi(argv[4]); |
| 147 | } |
| 148 | |
| 149 | /* |
| 150 | * Extract the hostname and port number from the URI... |
| 151 | */ |
| 152 | |
| 153 | while ((device_uri = cupsBackendDeviceURI(argv)) == NULL) |
| 154 | { |
| 155 | _cupsLangPrintFilter(stderr, "INFO", _("Unable to locate printer.")); |
| 156 | sleep(10); |
| 157 | |
| 158 | if (getenv("CLASS") != NULL) |
| 159 | return (CUPS_BACKEND_FAILED); |
| 160 | } |
| 161 | |
| 162 | httpSeparateURI(HTTP_URI_CODING_ALL, device_uri, scheme, sizeof(scheme), |
| 163 | username, sizeof(username), hostname, sizeof(hostname), &port, |
| 164 | resource, sizeof(resource)); |
| 165 | |
| 166 | if (port == 0) |
| 167 | port = 9100; /* Default to HP JetDirect/Tektronix PhaserShare */ |
| 168 | |
| 169 | /* |
| 170 | * Get options, if any... |
| 171 | */ |
| 172 | |
| 173 | waiteof = 1; |
| 174 | contimeout = 7 * 24 * 60 * 60; |
| 175 | |
| 176 | if ((options = strchr(resource, '?')) != NULL) |
| 177 | { |
| 178 | /* |
| 179 | * Yup, terminate the device name string and move to the first |
| 180 | * character of the options... |
| 181 | */ |
| 182 | |
| 183 | *options++ = '\0'; |
| 184 | |
| 185 | /* |
| 186 | * Parse options... |
| 187 | */ |
| 188 | |
| 189 | while (*options) |
| 190 | { |
| 191 | /* |
| 192 | * Get the name... |
| 193 | */ |
| 194 | |
| 195 | name = options; |
| 196 | |
| 197 | while (*options && *options != '=' && *options != '+' && *options != '&') |
| 198 | options ++; |
| 199 | |
| 200 | if ((sep = *options) != '\0') |
| 201 | *options++ = '\0'; |
| 202 | |
| 203 | if (sep == '=') |
| 204 | { |
| 205 | /* |
| 206 | * Get the value... |
| 207 | */ |
| 208 | |
| 209 | value = options; |
| 210 | |
| 211 | while (*options && *options != '+' && *options != '&') |
| 212 | options ++; |
| 213 | |
| 214 | if (*options) |
| 215 | *options++ = '\0'; |
| 216 | } |
| 217 | else |
| 218 | value = (char *)""; |
| 219 | |
| 220 | /* |
| 221 | * Process the option... |
| 222 | */ |
| 223 | |
| 224 | if (!_cups_strcasecmp(name, "waiteof")) |
| 225 | { |
| 226 | /* |
| 227 | * Set the wait-for-eof value... |
| 228 | */ |
| 229 | |
| 230 | waiteof = !value[0] || !_cups_strcasecmp(value, "on") || |
| 231 | !_cups_strcasecmp(value, "yes") || !_cups_strcasecmp(value, "true"); |
| 232 | } |
| 233 | else if (!_cups_strcasecmp(name, "snmp")) |
| 234 | { |
| 235 | /* |
| 236 | * Enable/disable SNMP stuff... |
| 237 | */ |
| 238 | |
| 239 | snmp_enabled = !value[0] || !_cups_strcasecmp(value, "on") || |
| 240 | !_cups_strcasecmp(value, "yes") || |
| 241 | !_cups_strcasecmp(value, "true"); |
| 242 | } |
| 243 | else if (!_cups_strcasecmp(name, "contimeout")) |
| 244 | { |
| 245 | /* |
| 246 | * Set the connection timeout... |
| 247 | */ |
| 248 | |
| 249 | if (atoi(value) > 0) |
| 250 | contimeout = atoi(value); |
| 251 | } |
| 252 | } |
| 253 | } |
| 254 | |
| 255 | /* |
| 256 | * Then try finding the remote host... |
| 257 | */ |
| 258 | |
| 259 | start_time = time(NULL); |
| 260 | |
| 261 | addrlist = backendLookup(hostname, port, NULL); |
| 262 | |
| 263 | /* |
| 264 | * See if the printer supports SNMP... |
| 265 | */ |
| 266 | |
| 267 | if (snmp_enabled) |
| 268 | snmp_fd = _cupsSNMPOpen(addrlist->addr.addr.sa_family); |
| 269 | else |
| 270 | snmp_fd = -1; |
| 271 | |
| 272 | if (snmp_fd >= 0) |
| 273 | have_supplies = !backendSNMPSupplies(snmp_fd, &(addrlist->addr), |
| 274 | &start_count, NULL); |
| 275 | else |
| 276 | have_supplies = start_count = 0; |
| 277 | |
| 278 | /* |
| 279 | * Wait for data from the filter... |
| 280 | */ |
| 281 | |
| 282 | if (print_fd == 0) |
| 283 | { |
| 284 | if (!backendWaitLoop(snmp_fd, &(addrlist->addr), 1, backendNetworkSideCB)) |
| 285 | return (CUPS_BACKEND_OK); |
| 286 | else if ((bytes = read(0, buffer, sizeof(buffer))) <= 0) |
| 287 | return (CUPS_BACKEND_OK); |
| 288 | } |
| 289 | |
| 290 | /* |
| 291 | * Connect to the printer... |
| 292 | */ |
| 293 | |
| 294 | fprintf(stderr, "DEBUG: Connecting to %s:%d\n", hostname, port); |
| 295 | _cupsLangPrintFilter(stderr, "INFO", _("Connecting to printer.")); |
| 296 | |
| 297 | for (delay = 5;;) |
| 298 | { |
| 299 | if ((addr = httpAddrConnect(addrlist, &device_fd)) == NULL) |
| 300 | { |
| 301 | error = errno; |
| 302 | device_fd = -1; |
| 303 | |
| 304 | if (getenv("CLASS") != NULL) |
| 305 | { |
| 306 | /* |
| 307 | * If the CLASS environment variable is set, the job was submitted |
| 308 | * to a class and not to a specific queue. In this case, we want |
| 309 | * to abort immediately so that the job can be requeued on the next |
| 310 | * available printer in the class. |
| 311 | */ |
| 312 | |
| 313 | _cupsLangPrintFilter(stderr, "INFO", |
| 314 | _("Unable to contact printer, queuing on next " |
| 315 | "printer in class.")); |
| 316 | |
| 317 | /* |
| 318 | * Sleep 5 seconds to keep the job from requeuing too rapidly... |
| 319 | */ |
| 320 | |
| 321 | sleep(5); |
| 322 | |
| 323 | return (CUPS_BACKEND_FAILED); |
| 324 | } |
| 325 | |
| 326 | fprintf(stderr, "DEBUG: Connection error: %s\n", strerror(error)); |
| 327 | |
| 328 | if (errno == ECONNREFUSED || errno == EHOSTDOWN || errno == EHOSTUNREACH || errno == ETIMEDOUT || errno == ENOTCONN) |
| 329 | { |
| 330 | if (contimeout && (time(NULL) - start_time) > contimeout) |
| 331 | { |
| 332 | _cupsLangPrintFilter(stderr, "ERROR", |
| 333 | _("The printer is not responding.")); |
| 334 | return (CUPS_BACKEND_FAILED); |
| 335 | } |
| 336 | |
| 337 | switch (error) |
| 338 | { |
| 339 | case EHOSTDOWN : |
| 340 | _cupsLangPrintFilter(stderr, "WARNING", |
| 341 | _("The printer may not exist or " |
| 342 | "is unavailable at this time.")); |
| 343 | break; |
| 344 | |
| 345 | case EHOSTUNREACH : |
| 346 | default : |
| 347 | _cupsLangPrintFilter(stderr, "WARNING", |
| 348 | _("The printer is unreachable at this " |
| 349 | "time.")); |
| 350 | break; |
| 351 | |
| 352 | case ECONNREFUSED : |
| 353 | _cupsLangPrintFilter(stderr, "WARNING", |
| 354 | _("The printer is in use.")); |
| 355 | break; |
| 356 | } |
| 357 | |
| 358 | sleep((unsigned)delay); |
| 359 | |
| 360 | if (delay < 30) |
| 361 | delay += 5; |
| 362 | } |
| 363 | else |
| 364 | { |
| 365 | _cupsLangPrintFilter(stderr, "ERROR", |
| 366 | _("The printer is not responding.")); |
| 367 | sleep(30); |
| 368 | } |
| 369 | } |
| 370 | else |
| 371 | break; |
| 372 | } |
| 373 | |
| 374 | fputs("STATE: -connecting-to-device\n", stderr); |
| 375 | _cupsLangPrintFilter(stderr, "INFO", _("Connected to printer.")); |
| 376 | |
| 377 | fprintf(stderr, "DEBUG: Connected to %s:%d...\n", |
| 378 | httpAddrString(&(addr->addr), addrname, sizeof(addrname)), |
| 379 | httpAddrPort(&(addr->addr))); |
| 380 | |
| 381 | /* |
| 382 | * Print everything... |
| 383 | */ |
| 384 | |
| 385 | tbytes = 0; |
| 386 | |
| 387 | if (bytes > 0) |
| 388 | tbytes += write(device_fd, buffer, (size_t)bytes); |
| 389 | |
| 390 | while (copies > 0 && tbytes >= 0) |
| 391 | { |
| 392 | copies --; |
| 393 | |
| 394 | if (print_fd != 0) |
| 395 | { |
| 396 | fputs("PAGE: 1 1\n", stderr); |
| 397 | lseek(print_fd, 0, SEEK_SET); |
| 398 | } |
| 399 | |
| 400 | if ((bytes = backendRunLoop(print_fd, device_fd, snmp_fd, &(addrlist->addr), 1, 0, backendNetworkSideCB)) < 0) |
| 401 | tbytes = -1; |
| 402 | else |
| 403 | tbytes = bytes; |
| 404 | |
| 405 | if (print_fd != 0 && tbytes >= 0) |
| 406 | _cupsLangPrintFilter(stderr, "INFO", _("Print file sent.")); |
| 407 | } |
| 408 | |
| 409 | fputs("STATE: +cups-waiting-for-job-completed\n", stderr); |
| 410 | |
| 411 | if (waiteof && tbytes >= 0) |
| 412 | { |
| 413 | /* |
| 414 | * Shutdown the socket and wait for the other end to finish... |
| 415 | */ |
| 416 | |
| 417 | _cupsLangPrintFilter(stderr, "INFO", _("Waiting for printer to finish.")); |
| 418 | |
| 419 | shutdown(device_fd, 1); |
| 420 | |
| 421 | while (wait_bc(device_fd, 90) > 0); |
| 422 | } |
| 423 | |
| 424 | /* |
| 425 | * Collect the final page count as needed... |
| 426 | */ |
| 427 | |
| 428 | if (have_supplies && |
| 429 | !backendSNMPSupplies(snmp_fd, &(addrlist->addr), &page_count, NULL) && |
| 430 | page_count > start_count) |
| 431 | fprintf(stderr, "PAGE: total %d\n", page_count - start_count); |
| 432 | |
| 433 | /* |
| 434 | * Close the socket connection... |
| 435 | */ |
| 436 | |
| 437 | close(device_fd); |
| 438 | |
| 439 | httpAddrFreeList(addrlist); |
| 440 | |
| 441 | /* |
| 442 | * Close the input file and return... |
| 443 | */ |
| 444 | |
| 445 | if (print_fd != 0) |
| 446 | close(print_fd); |
| 447 | |
| 448 | return (tbytes >= 0 ? CUPS_BACKEND_OK : CUPS_BACKEND_FAILED); |
| 449 | } |
| 450 | |
| 451 | |
| 452 | /* |
| 453 | * 'wait_bc()' - Wait for back-channel data... |
| 454 | */ |
| 455 | |
| 456 | static ssize_t /* O - # bytes read or -1 on error */ |
| 457 | wait_bc(int device_fd, /* I - Socket */ |
| 458 | int secs) /* I - Seconds to wait */ |
| 459 | { |
| 460 | struct timeval timeout; /* Timeout for select() */ |
| 461 | fd_set input; /* Input set for select() */ |
| 462 | ssize_t bytes; /* Number of back-channel bytes read */ |
| 463 | char buffer[1024]; /* Back-channel buffer */ |
| 464 | |
| 465 | |
| 466 | /* |
| 467 | * Wait up to "secs" seconds for backchannel data... |
| 468 | */ |
| 469 | |
| 470 | timeout.tv_sec = secs; |
| 471 | timeout.tv_usec = 0; |
| 472 | |
| 473 | FD_ZERO(&input); |
| 474 | FD_SET(device_fd, &input); |
| 475 | |
| 476 | if (select(device_fd + 1, &input, NULL, NULL, &timeout) > 0) |
| 477 | { |
| 478 | /* |
| 479 | * Grab the data coming back and spit it out to stderr... |
| 480 | */ |
| 481 | |
| 482 | if ((bytes = read(device_fd, buffer, sizeof(buffer))) > 0) |
| 483 | { |
| 484 | fprintf(stderr, "DEBUG: Received %d bytes of back-channel data\n", |
| 485 | (int)bytes); |
| 486 | cupsBackChannelWrite(buffer, (size_t)bytes, 1.0); |
| 487 | } |
| 488 | |
| 489 | return (bytes); |
| 490 | } |
| 491 | else |
| 492 | return (-1); |
| 493 | } |