]> git.ipfire.org Git - thirdparty/cups.git/blob - backend/ipp.c
b42b0dff1a55ff27e855d2e2cb95afe743b250d3
[thirdparty/cups.git] / backend / ipp.c
1 /*
2 * IPP backend for CUPS.
3 *
4 * Copyright 2007-2016 by Apple Inc.
5 * Copyright 1997-2007 by Easy Software Products, all rights reserved.
6 *
7 * These coded instructions, statements, and computer programs are the
8 * property of Apple Inc. and are protected by Federal copyright
9 * law. Distribution and use rights are outlined in the file "LICENSE.txt"
10 * "LICENSE" which should have been included with this file. If this
11 * file is missing or damaged, see the license at "http://www.cups.org/".
12 *
13 * This file is subject to the Apple OS-Developed Software exception.
14 */
15
16 /*
17 * Include necessary headers.
18 */
19
20 #include "backend-private.h"
21 #include <cups/ppd-private.h>
22 #include <cups/array-private.h>
23 #include <sys/types.h>
24 #include <sys/stat.h>
25 #include <sys/wait.h>
26 #if defined(HAVE_GSSAPI) && defined(HAVE_XPC)
27 # include <xpc/xpc.h>
28 # define kPMPrintUIToolAgent "com.apple.printuitool.agent"
29 # define kPMStartJob 100
30 # define kPMWaitForJob 101
31 # ifdef HAVE_XPC_PRIVATE_H
32 # include <xpc/private.h>
33 # else
34 extern void xpc_connection_set_target_uid(xpc_connection_t connection,
35 uid_t uid);
36 # endif /* HAVE_XPC_PRIVATE_H */
37 #endif /* HAVE_GSSAPI && HAVE_XPC */
38
39
40 /*
41 * Bits for job-state-reasons we care about...
42 */
43
44 #define _CUPS_JSR_ACCOUNT_AUTHORIZATION_FAILED 0x01
45 #define _CUPS_JSR_ACCOUNT_CLOSED 0x02
46 #define _CUPS_JSR_ACCOUNT_INFO_NEEDED 0x04
47 #define _CUPS_JSR_ACCOUNT_LIMIT_REACHED 0x08
48 #define _CUPS_JSR_JOB_PASSWORD_WAIT 0x10
49 #define _CUPS_JSR_JOB_RELEASE_WAIT 0x20
50
51
52 /*
53 * Types...
54 */
55
56 typedef struct _cups_monitor_s /**** Monitoring data ****/
57 {
58 const char *uri, /* Printer URI */
59 *hostname, /* Hostname */
60 *user, /* Username */
61 *resource; /* Resource path */
62 int port, /* Port number */
63 version, /* IPP version */
64 job_id, /* Job ID for submitted job */
65 job_reasons, /* Job state reasons bits */
66 create_job, /* Support Create-Job? */
67 get_job_attrs; /* Support Get-Job-Attributes? */
68 const char *job_name; /* Job name for submitted job */
69 http_encryption_t encryption; /* Use encryption? */
70 ipp_jstate_t job_state; /* Current job state */
71 ipp_pstate_t printer_state; /* Current printer state */
72 } _cups_monitor_t;
73
74
75 /*
76 * Globals...
77 */
78
79 static const char *auth_info_required;
80 /* New auth-info-required value */
81 #if defined(HAVE_GSSAPI) && defined(HAVE_XPC)
82 static pid_t child_pid = 0; /* Child process ID */
83 #endif /* HAVE_GSSAPI && HAVE_XPC */
84 static const char * const jattrs[] = /* Job attributes we want */
85 {
86 "job-id",
87 "job-impressions-completed",
88 "job-media-sheets-completed",
89 "job-name",
90 "job-originating-user-name",
91 "job-state",
92 "job-state-reasons"
93 };
94 static int job_canceled = 0,
95 /* Job cancelled? */
96 uri_credentials = 0;
97 /* Credentials supplied in URI? */
98 static char username[256] = "",
99 /* Username for device URI */
100 *password = NULL;
101 /* Password for device URI */
102 static const char * const pattrs[] = /* Printer attributes we want */
103 {
104 #ifdef HAVE_LIBZ
105 "compression-supported",
106 #endif /* HAVE_LIBZ */
107 "copies-supported",
108 "cups-version",
109 "document-format-supported",
110 "job-password-encryption-supported",
111 "marker-colors",
112 "marker-high-levels",
113 "marker-levels",
114 "marker-low-levels",
115 "marker-message",
116 "marker-names",
117 "marker-types",
118 "media-col-supported",
119 "multiple-document-handling-supported",
120 "operations-supported",
121 "print-color-mode-supported",
122 "printer-alert",
123 "printer-alert-description",
124 "printer-is-accepting-jobs",
125 "printer-mandatory-job-attributes",
126 "printer-state",
127 "printer-state-message",
128 "printer-state-reasons"
129 };
130 static const char * const remote_job_states[] =
131 { /* Remote job state keywords */
132 "+cups-remote-pending",
133 "+cups-remote-pending-held",
134 "+cups-remote-processing",
135 "+cups-remote-stopped",
136 "+cups-remote-canceled",
137 "+cups-remote-aborted",
138 "+cups-remote-completed"
139 };
140 static _cups_mutex_t report_mutex = _CUPS_MUTEX_INITIALIZER;
141 /* Mutex to control access */
142 static int num_attr_cache = 0;
143 /* Number of cached attributes */
144 static cups_option_t *attr_cache = NULL;
145 /* Cached attributes */
146 static cups_array_t *state_reasons; /* Array of printe-state-reasons keywords */
147 static char tmpfilename[1024] = "";
148 /* Temporary spool file name */
149 static char mandatory_attrs[1024] = "";
150 /* cupsMandatory value */
151
152
153 /*
154 * Local functions...
155 */
156
157 static void cancel_job(http_t *http, const char *uri, int id,
158 const char *resource, const char *user,
159 int version);
160 static ipp_pstate_t check_printer_state(http_t *http, const char *uri,
161 const char *resource,
162 const char *user, int version);
163 static void *monitor_printer(_cups_monitor_t *monitor);
164 static ipp_t *new_request(ipp_op_t op, int version, const char *uri,
165 const char *user, const char *title,
166 int num_options, cups_option_t *options,
167 const char *compression, int copies,
168 const char *format, _ppd_cache_t *pc,
169 ppd_file_t *ppd,
170 ipp_attribute_t *media_col_sup,
171 ipp_attribute_t *doc_handling_sup,
172 ipp_attribute_t *print_color_mode_sup);
173 static const char *password_cb(const char *prompt, http_t *http,
174 const char *method, const char *resource,
175 int *user_data);
176 static const char *quote_string(const char *s, char *q, size_t qsize);
177 static void report_attr(ipp_attribute_t *attr);
178 static void report_printer_state(ipp_t *ipp);
179 #if defined(HAVE_GSSAPI) && defined(HAVE_XPC)
180 static int run_as_user(char *argv[], uid_t uid,
181 const char *device_uri, int fd);
182 #endif /* HAVE_GSSAPI && HAVE_XPC */
183 static void sigterm_handler(int sig);
184 static int timeout_cb(http_t *http, void *user_data);
185 static void update_reasons(ipp_attribute_t *attr, const char *s);
186
187
188 /*
189 * 'main()' - Send a file to the printer or server.
190 *
191 * Usage:
192 *
193 * printer-uri job-id user title copies options [file]
194 */
195
196 int /* O - Exit status */
197 main(int argc, /* I - Number of command-line args */
198 char *argv[]) /* I - Command-line arguments */
199 {
200 int i; /* Looping var */
201 int send_options; /* Send job options? */
202 int num_options; /* Number of printer options */
203 cups_option_t *options; /* Printer options */
204 const char *device_uri; /* Device URI */
205 char scheme[255], /* Scheme in URI */
206 hostname[1024], /* Hostname */
207 resource[1024], /* Resource info (printer name) */
208 addrname[256], /* Address name */
209 *optptr, /* Pointer to URI options */
210 *name, /* Name of option */
211 *value, /* Value of option */
212 sep; /* Separator character */
213 int password_tries = 0; /* Password tries */
214 http_addrlist_t *addrlist; /* Address of printer */
215 int snmp_enabled = 1; /* Is SNMP enabled? */
216 int snmp_fd, /* SNMP socket */
217 start_count, /* Page count via SNMP at start */
218 page_count, /* Page count via SNMP */
219 have_supplies; /* Printer supports supply levels? */
220 int num_files; /* Number of files to print */
221 char **files, /* Files to print */
222 *compatfile = NULL; /* Compatibility filename */
223 off_t compatsize = 0; /* Size of compatibility file */
224 int port; /* Port number (not used) */
225 char portname[255]; /* Port name */
226 char uri[HTTP_MAX_URI]; /* Updated URI without user/pass */
227 char print_job_name[1024]; /* Update job-name for Print-Job */
228 http_status_t http_status; /* Status of HTTP request */
229 ipp_status_t ipp_status; /* Status of IPP request */
230 http_t *http; /* HTTP connection */
231 ipp_t *request, /* IPP request */
232 *response, /* IPP response */
233 *supported; /* get-printer-attributes response */
234 time_t start_time; /* Time of first connect */
235 int contimeout; /* Connection timeout */
236 int delay, /* Delay for retries */
237 prev_delay; /* Previous delay */
238 const char *compression; /* Compression mode */
239 int waitjob, /* Wait for job complete? */
240 waitjob_tries = 0, /* Number of times we've waited */
241 waitprinter; /* Wait for printer ready? */
242 time_t waittime; /* Wait time for held jobs */
243 _cups_monitor_t monitor; /* Monitoring data */
244 ipp_attribute_t *job_id_attr; /* job-id attribute */
245 int job_id; /* job-id value */
246 ipp_attribute_t *job_sheets; /* job-media-sheets-completed */
247 ipp_attribute_t *job_state; /* job-state */
248 #ifdef HAVE_LIBZ
249 ipp_attribute_t *compression_sup; /* compression-supported */
250 #endif /* HAVE_LIBZ */
251 ipp_attribute_t *copies_sup; /* copies-supported */
252 ipp_attribute_t *cups_version; /* cups-version */
253 ipp_attribute_t *encryption_sup; /* job-password-encryption-supported */
254 ipp_attribute_t *format_sup; /* document-format-supported */
255 ipp_attribute_t *job_auth; /* job-authorization-uri */
256 ipp_attribute_t *media_col_sup; /* media-col-supported */
257 ipp_attribute_t *operations_sup; /* operations-supported */
258 ipp_attribute_t *doc_handling_sup; /* multiple-document-handling-supported */
259 ipp_attribute_t *printer_state; /* printer-state attribute */
260 ipp_attribute_t *printer_accepting; /* printer-is-accepting-jobs */
261 ipp_attribute_t *print_color_mode_sup;/* Does printer support print-color-mode? */
262 int create_job = 0, /* Does printer support Create-Job? */
263 get_job_attrs = 0, /* Does printer support Get-Job-Attributes? */
264 send_document = 0, /* Does printer support Send-Document? */
265 validate_job = 0, /* Does printer support Validate-Job? */
266 copies, /* Number of copies for job */
267 copies_remaining; /* Number of copies remaining */
268 const char *content_type, /* CONTENT_TYPE environment variable */
269 *final_content_type, /* FINAL_CONTENT_TYPE environment var */
270 *document_format; /* document-format value */
271 int fd; /* File descriptor */
272 off_t bytes = 0; /* Bytes copied */
273 char buffer[16384]; /* Copy buffer */
274 #if defined(HAVE_SIGACTION) && !defined(HAVE_SIGSET)
275 struct sigaction action; /* Actions for POSIX signals */
276 #endif /* HAVE_SIGACTION && !HAVE_SIGSET */
277 int version; /* IPP version */
278 ppd_file_t *ppd = NULL; /* PPD file */
279 _ppd_cache_t *pc = NULL; /* PPD cache and mapping data */
280 fd_set input; /* Input set for select() */
281
282
283 /*
284 * Make sure status messages are not buffered...
285 */
286
287 setbuf(stderr, NULL);
288
289 /*
290 * Ignore SIGPIPE and catch SIGTERM signals...
291 */
292
293 #ifdef HAVE_SIGSET
294 sigset(SIGPIPE, SIG_IGN);
295 sigset(SIGTERM, sigterm_handler);
296 #elif defined(HAVE_SIGACTION)
297 memset(&action, 0, sizeof(action));
298 action.sa_handler = SIG_IGN;
299 sigaction(SIGPIPE, &action, NULL);
300
301 sigemptyset(&action.sa_mask);
302 sigaddset(&action.sa_mask, SIGTERM);
303 action.sa_handler = sigterm_handler;
304 sigaction(SIGTERM, &action, NULL);
305 #else
306 signal(SIGPIPE, SIG_IGN);
307 signal(SIGTERM, sigterm_handler);
308 #endif /* HAVE_SIGSET */
309
310 /*
311 * Check command-line...
312 */
313
314 if (argc == 1)
315 {
316 char *s;
317
318 if ((s = strrchr(argv[0], '/')) != NULL)
319 s ++;
320 else
321 s = argv[0];
322
323 printf("network %s \"Unknown\" \"%s (%s)\"\n",
324 s, _cupsLangString(cupsLangDefault(),
325 _("Internet Printing Protocol")), s);
326 return (CUPS_BACKEND_OK);
327 }
328 else if (argc < 6)
329 {
330 _cupsLangPrintf(stderr,
331 _("Usage: %s job-id user title copies options [file]"),
332 argv[0]);
333 return (CUPS_BACKEND_STOP);
334 }
335
336 /*
337 * Get the device URI...
338 */
339
340 while ((device_uri = cupsBackendDeviceURI(argv)) == NULL)
341 {
342 _cupsLangPrintFilter(stderr, "INFO", _("Unable to locate printer."));
343 sleep(10);
344
345 if (getenv("CLASS") != NULL)
346 return (CUPS_BACKEND_FAILED);
347 }
348
349 if ((auth_info_required = getenv("AUTH_INFO_REQUIRED")) == NULL)
350 auth_info_required = "none";
351
352 state_reasons = _cupsArrayNewStrings(getenv("PRINTER_STATE_REASONS"), ',');
353
354 #ifdef HAVE_GSSAPI
355 /*
356 * For Kerberos, become the printing user (if we can) to get the credentials
357 * that way.
358 */
359
360 if (!getuid() && (value = getenv("AUTH_UID")) != NULL &&
361 !getenv("AUTH_PASSWORD"))
362 {
363 uid_t uid = (uid_t)atoi(value);
364 /* User ID */
365
366 # ifdef HAVE_XPC
367 if (uid > 0)
368 {
369 if (argc == 6)
370 return (run_as_user(argv, uid, device_uri, 0));
371 else
372 {
373 int status = 0; /* Exit status */
374
375 for (i = 6; i < argc && !status && !job_canceled; i ++)
376 {
377 if ((fd = open(argv[i], O_RDONLY)) >= 0)
378 {
379 status = run_as_user(argv, uid, device_uri, fd);
380 close(fd);
381 }
382 else
383 {
384 _cupsLangPrintError("ERROR", _("Unable to open print file"));
385 status = CUPS_BACKEND_FAILED;
386 }
387 }
388
389 return (status);
390 }
391 }
392
393 # else /* No XPC, just try to run as the user ID */
394 if (uid > 0)
395 seteuid(uid);
396 # endif /* HAVE_XPC */
397 }
398 #endif /* HAVE_GSSAPI */
399
400 /*
401 * Get the (final) content type...
402 */
403
404 if ((content_type = getenv("CONTENT_TYPE")) == NULL)
405 content_type = "application/octet-stream";
406
407 if ((final_content_type = getenv("FINAL_CONTENT_TYPE")) == NULL)
408 {
409 final_content_type = content_type;
410
411 if (!strncmp(final_content_type, "printer/", 8))
412 final_content_type = "application/vnd.cups-raw";
413 }
414
415 /*
416 * Extract the hostname and printer name from the URI...
417 */
418
419 httpSeparateURI(HTTP_URI_CODING_ALL, device_uri, scheme, sizeof(scheme),
420 username, sizeof(username), hostname, sizeof(hostname), &port,
421 resource, sizeof(resource));
422
423 if (!port)
424 port = IPP_PORT; /* Default to port 631 */
425
426 if (!strcmp(scheme, "https") || !strcmp(scheme, "ipps"))
427 cupsSetEncryption(HTTP_ENCRYPT_ALWAYS);
428 else
429 cupsSetEncryption(HTTP_ENCRYPT_IF_REQUESTED);
430
431 /*
432 * See if there are any options...
433 */
434
435 compression = NULL;
436 version = 20;
437 waitjob = 1;
438 waitprinter = 1;
439 contimeout = 7 * 24 * 60 * 60;
440
441 if ((optptr = strchr(resource, '?')) != NULL)
442 {
443 /*
444 * Yup, terminate the device name string and move to the first
445 * character of the optptr...
446 */
447
448 *optptr++ = '\0';
449
450 /*
451 * Then parse the optptr...
452 */
453
454 while (*optptr)
455 {
456 /*
457 * Get the name...
458 */
459
460 name = optptr;
461
462 while (*optptr && *optptr != '=' && *optptr != '+' && *optptr != '&')
463 optptr ++;
464
465 if ((sep = *optptr) != '\0')
466 *optptr++ = '\0';
467
468 if (sep == '=')
469 {
470 /*
471 * Get the value...
472 */
473
474 value = optptr;
475
476 while (*optptr && *optptr != '+' && *optptr != '&')
477 optptr ++;
478
479 if (*optptr)
480 *optptr++ = '\0';
481 }
482 else
483 value = (char *)"";
484
485 /*
486 * Process the option...
487 */
488
489 if (!_cups_strcasecmp(name, "waitjob"))
490 {
491 /*
492 * Wait for job completion?
493 */
494
495 waitjob = !_cups_strcasecmp(value, "on") ||
496 !_cups_strcasecmp(value, "yes") ||
497 !_cups_strcasecmp(value, "true");
498 }
499 else if (!_cups_strcasecmp(name, "waitprinter"))
500 {
501 /*
502 * Wait for printer idle?
503 */
504
505 waitprinter = !_cups_strcasecmp(value, "on") ||
506 !_cups_strcasecmp(value, "yes") ||
507 !_cups_strcasecmp(value, "true");
508 }
509 else if (!_cups_strcasecmp(name, "encryption"))
510 {
511 /*
512 * Enable/disable encryption?
513 */
514
515 if (!_cups_strcasecmp(value, "always"))
516 cupsSetEncryption(HTTP_ENCRYPT_ALWAYS);
517 else if (!_cups_strcasecmp(value, "required"))
518 cupsSetEncryption(HTTP_ENCRYPT_REQUIRED);
519 else if (!_cups_strcasecmp(value, "never"))
520 cupsSetEncryption(HTTP_ENCRYPT_NEVER);
521 else if (!_cups_strcasecmp(value, "ifrequested"))
522 cupsSetEncryption(HTTP_ENCRYPT_IF_REQUESTED);
523 else
524 {
525 _cupsLangPrintFilter(stderr, "ERROR",
526 _("Unknown encryption option value: \"%s\"."),
527 value);
528 }
529 }
530 else if (!_cups_strcasecmp(name, "snmp"))
531 {
532 /*
533 * Enable/disable SNMP stuff...
534 */
535
536 snmp_enabled = !value[0] || !_cups_strcasecmp(value, "on") ||
537 !_cups_strcasecmp(value, "yes") ||
538 !_cups_strcasecmp(value, "true");
539 }
540 else if (!_cups_strcasecmp(name, "version"))
541 {
542 if (!strcmp(value, "1.0"))
543 version = 10;
544 else if (!strcmp(value, "1.1"))
545 version = 11;
546 else if (!strcmp(value, "2.0"))
547 version = 20;
548 else if (!strcmp(value, "2.1"))
549 version = 21;
550 else if (!strcmp(value, "2.2"))
551 version = 22;
552 else
553 {
554 _cupsLangPrintFilter(stderr, "ERROR",
555 _("Unknown version option value: \"%s\"."),
556 value);
557 }
558 }
559 #ifdef HAVE_LIBZ
560 else if (!_cups_strcasecmp(name, "compression"))
561 {
562 if (!_cups_strcasecmp(value, "true") || !_cups_strcasecmp(value, "yes") ||
563 !_cups_strcasecmp(value, "on") || !_cups_strcasecmp(value, "gzip"))
564 compression = "gzip";
565 else if (!_cups_strcasecmp(value, "deflate"))
566 compression = "deflate";
567 else if (!_cups_strcasecmp(value, "false") ||
568 !_cups_strcasecmp(value, "no") ||
569 !_cups_strcasecmp(value, "off") ||
570 !_cups_strcasecmp(value, "none"))
571 compression = "none";
572 }
573 #endif /* HAVE_LIBZ */
574 else if (!_cups_strcasecmp(name, "contimeout"))
575 {
576 /*
577 * Set the connection timeout...
578 */
579
580 if (atoi(value) > 0)
581 contimeout = atoi(value);
582 }
583 else
584 {
585 /*
586 * Unknown option...
587 */
588
589 _cupsLangPrintFilter(stderr, "ERROR",
590 _("Unknown option \"%s\" with value \"%s\"."),
591 name, value);
592 }
593 }
594 }
595
596 /*
597 * If we have 7 arguments, print the file named on the command-line.
598 * Otherwise, copy stdin to a temporary file and print the temporary
599 * file.
600 */
601
602 if (argc == 6)
603 {
604 num_files = 0;
605 files = NULL;
606 send_options = !_cups_strcasecmp(final_content_type, "application/pdf") ||
607 !_cups_strcasecmp(final_content_type, "application/vnd.cups-pdf") ||
608 !_cups_strncasecmp(final_content_type, "image/", 6);
609
610 fputs("DEBUG: Sending stdin for job...\n", stderr);
611 }
612 else
613 {
614 /*
615 * Point to the files on the command-line...
616 */
617
618 num_files = argc - 6;
619 files = argv + 6;
620 send_options = 1;
621
622 fprintf(stderr, "DEBUG: %d files to send in job...\n", num_files);
623 }
624
625 /*
626 * Set the authentication info, if any...
627 */
628
629 cupsSetPasswordCB2((cups_password_cb2_t)password_cb, &password_tries);
630
631 if (username[0])
632 {
633 /*
634 * Use authentication information in the device URI...
635 */
636
637 if ((password = strchr(username, ':')) != NULL)
638 *password++ = '\0';
639
640 cupsSetUser(username);
641 uri_credentials = 1;
642 }
643 else
644 {
645 /*
646 * Try loading authentication information from the environment.
647 */
648
649 const char *ptr = getenv("AUTH_USERNAME");
650
651 if (ptr)
652 {
653 strlcpy(username, ptr, sizeof(username));
654 cupsSetUser(ptr);
655 }
656
657 password = getenv("AUTH_PASSWORD");
658 }
659
660 /*
661 * Try finding the remote server...
662 */
663
664 start_time = time(NULL);
665
666 sprintf(portname, "%d", port);
667
668 update_reasons(NULL, "+connecting-to-device");
669 fprintf(stderr, "DEBUG: Looking up \"%s\"...\n", hostname);
670
671 while ((addrlist = httpAddrGetList(hostname, AF_UNSPEC, portname)) == NULL)
672 {
673 _cupsLangPrintFilter(stderr, "INFO",
674 _("Unable to locate printer \"%s\"."), hostname);
675 sleep(10);
676
677 if (getenv("CLASS") != NULL)
678 {
679 update_reasons(NULL, "-connecting-to-device");
680 return (CUPS_BACKEND_STOP);
681 }
682
683 if (job_canceled)
684 return (CUPS_BACKEND_OK);
685 }
686
687 http = httpConnect2(hostname, port, addrlist, AF_UNSPEC, cupsEncryption(), 1,
688 0, NULL);
689 httpSetTimeout(http, 30.0, timeout_cb, NULL);
690
691 /*
692 * See if the printer supports SNMP...
693 */
694
695 if (snmp_enabled)
696 snmp_fd = _cupsSNMPOpen(addrlist->addr.addr.sa_family);
697 else
698 snmp_fd = -1;
699
700 if (snmp_fd >= 0)
701 have_supplies = !backendSNMPSupplies(snmp_fd, &(addrlist->addr),
702 &start_count, NULL);
703 else
704 have_supplies = start_count = 0;
705
706 /*
707 * Wait for data from the filter...
708 */
709
710 if (num_files == 0)
711 {
712 if (!backendWaitLoop(snmp_fd, &(addrlist->addr), 0, backendNetworkSideCB))
713 return (CUPS_BACKEND_OK);
714 else if ((bytes = read(0, buffer, sizeof(buffer))) <= 0)
715 return (CUPS_BACKEND_OK);
716 }
717
718 /*
719 * Try connecting to the remote server...
720 */
721
722 delay = _cupsNextDelay(0, &prev_delay);
723
724 do
725 {
726 fprintf(stderr, "DEBUG: Connecting to %s:%d\n", hostname, port);
727 _cupsLangPrintFilter(stderr, "INFO", _("Connecting to printer."));
728
729 if (httpReconnect(http))
730 {
731 int error = errno; /* Connection error */
732
733 if (http->status == HTTP_PKI_ERROR)
734 update_reasons(NULL, "+cups-certificate-error");
735
736 if (job_canceled)
737 break;
738
739 if (getenv("CLASS") != NULL)
740 {
741 /*
742 * If the CLASS environment variable is set, the job was submitted
743 * to a class and not to a specific queue. In this case, we want
744 * to abort immediately so that the job can be requeued on the next
745 * available printer in the class.
746 */
747
748 _cupsLangPrintFilter(stderr, "INFO",
749 _("Unable to contact printer, queuing on next "
750 "printer in class."));
751
752 /*
753 * Sleep 5 seconds to keep the job from requeuing too rapidly...
754 */
755
756 sleep(5);
757
758 update_reasons(NULL, "-connecting-to-device");
759
760 return (CUPS_BACKEND_FAILED);
761 }
762
763 fprintf(stderr, "DEBUG: Connection error: %s\n", strerror(errno));
764
765 if (errno == ECONNREFUSED || errno == EHOSTDOWN ||
766 errno == EHOSTUNREACH)
767 {
768 if (contimeout && (time(NULL) - start_time) > contimeout)
769 {
770 _cupsLangPrintFilter(stderr, "ERROR",
771 _("The printer is not responding."));
772 update_reasons(NULL, "-connecting-to-device");
773 return (CUPS_BACKEND_FAILED);
774 }
775
776 switch (error)
777 {
778 case EHOSTDOWN :
779 _cupsLangPrintFilter(stderr, "WARNING",
780 _("The printer may not exist or "
781 "is unavailable at this time."));
782 break;
783
784 case EHOSTUNREACH :
785 _cupsLangPrintFilter(stderr, "WARNING",
786 _("The printer is unreachable at this "
787 "time."));
788 break;
789
790 case ECONNREFUSED :
791 default :
792 _cupsLangPrintFilter(stderr, "WARNING",
793 _("The printer is in use."));
794 break;
795 }
796
797 sleep((unsigned)delay);
798
799 delay = _cupsNextDelay(delay, &prev_delay);
800 }
801 else
802 {
803 _cupsLangPrintFilter(stderr, "ERROR",
804 _("The printer is not responding."));
805 sleep(30);
806 }
807
808 if (job_canceled)
809 break;
810 }
811 else
812 update_reasons(NULL, "-cups-certificate-error");
813 }
814 while (http->fd < 0);
815
816 if (job_canceled)
817 return (CUPS_BACKEND_OK);
818 else if (!http)
819 return (CUPS_BACKEND_FAILED);
820
821 if (httpIsEncrypted(http))
822 {
823 /*
824 * Validate TLS credentials...
825 */
826
827 cups_array_t *creds; /* TLS credentials */
828 cups_array_t *lcreds = NULL; /* Loaded credentials */
829 http_trust_t trust; /* Trust level */
830 char credinfo[1024], /* Information on credentials */
831 lcredinfo[1024];/* Information on saved credentials */
832 static const char * const trusts[] = { NULL, "+cups-pki-invalid", "+cups-pki-changed", "+cups-pki-expired", NULL, "+cups-pki-unknown" };
833 /* Trust keywords */
834 static const char * const trust_msgs[] =
835 {
836 "Credentials are OK/trusted",
837 "Credentials are invalid",
838 "Credentials have changed",
839 "Credentials are expired",
840 "Credentials have been renewed",
841 "Credentials are unknown/new"
842 };
843
844 fputs("DEBUG: Connection is encrypted.\n", stderr);
845
846 if (!httpCopyCredentials(http, &creds))
847 {
848 trust = httpCredentialsGetTrust(creds, hostname);
849 httpCredentialsString(creds, credinfo, sizeof(credinfo));
850
851 fprintf(stderr, "DEBUG: %s\n", trust_msgs[trust]);
852 fprintf(stderr, "DEBUG: Printer credentials: %s\n", credinfo);
853
854 if (!httpLoadCredentials(NULL, &lcreds, hostname))
855 {
856 httpCredentialsString(lcreds, lcredinfo, sizeof(lcredinfo));
857 fprintf(stderr, "DEBUG: Stored credentials: %s\n", lcredinfo);
858 }
859 else
860 fputs("DEBUG: No stored credentials.\n", stderr);
861
862 update_reasons(NULL, "-cups-pki-invalid,cups-pki-changed,cups-pki-expired,cups-pki-unknown");
863 if (trusts[trust])
864 {
865 update_reasons(NULL, trusts[trust]);
866 return (CUPS_BACKEND_STOP);
867 }
868
869 if (!lcreds)
870 {
871 /*
872 * Could not load the credentials, let's save the ones we have so we
873 * can detect changes...
874 */
875
876 httpSaveCredentials(NULL, creds, hostname);
877 }
878
879 httpFreeCredentials(lcreds);
880 httpFreeCredentials(creds);
881 }
882 else
883 {
884 fputs("DEBUG: No printer credentials.\n", stderr);
885
886 update_reasons(NULL, "cups-pki-unknown");
887 return (CUPS_BACKEND_STOP);
888 }
889 }
890
891 update_reasons(NULL, "-connecting-to-device");
892 _cupsLangPrintFilter(stderr, "INFO", _("Connected to printer."));
893
894 fprintf(stderr, "DEBUG: Connected to %s:%d...\n",
895 httpAddrString(http->hostaddr, addrname, sizeof(addrname)),
896 httpAddrPort(http->hostaddr));
897
898 /*
899 * Build a URI for the printer and fill the standard IPP attributes for
900 * an IPP_PRINT_FILE request. We can't use the URI in argv[0] because it
901 * might contain username:password information...
902 */
903
904 httpAssembleURI(HTTP_URI_CODING_ALL, uri, sizeof(uri), scheme, NULL, hostname,
905 port, resource);
906
907 /*
908 * First validate the destination and see if the device supports multiple
909 * copies...
910 */
911
912 #ifdef HAVE_LIBZ
913 compression_sup = NULL;
914 #endif /* HAVE_LIBZ */
915 copies_sup = NULL;
916 cups_version = NULL;
917 encryption_sup = NULL;
918 format_sup = NULL;
919 media_col_sup = NULL;
920 supported = NULL;
921 operations_sup = NULL;
922 doc_handling_sup = NULL;
923 print_color_mode_sup = NULL;
924
925 do
926 {
927 /*
928 * Check for side-channel requests...
929 */
930
931 backendCheckSideChannel(snmp_fd, http->hostaddr);
932
933 /*
934 * Build the IPP request...
935 */
936
937 request = ippNewRequest(IPP_GET_PRINTER_ATTRIBUTES);
938 ippSetVersion(request, version / 10, version % 10);
939 ippAddString(request, IPP_TAG_OPERATION, IPP_TAG_URI, "printer-uri",
940 NULL, uri);
941
942 ippAddStrings(request, IPP_TAG_OPERATION, IPP_TAG_KEYWORD,
943 "requested-attributes", sizeof(pattrs) / sizeof(pattrs[0]),
944 NULL, pattrs);
945
946 /*
947 * Do the request...
948 */
949
950 fputs("DEBUG: Getting supported attributes...\n", stderr);
951
952 if (http->version < HTTP_1_1)
953 {
954 fprintf(stderr, "DEBUG: Printer responded with HTTP version %d.%d.\n",
955 http->version / 100, http->version % 100);
956 update_reasons(NULL, "+cups-ipp-conformance-failure-report,"
957 "cups-ipp-wrong-http-version");
958 }
959
960 supported = cupsDoRequest(http, request, resource);
961 ipp_status = cupsLastError();
962
963 fprintf(stderr, "DEBUG: Get-Printer-Attributes: %s (%s)\n",
964 ippErrorString(ipp_status), cupsLastErrorString());
965
966 if (ipp_status <= IPP_OK_CONFLICT)
967 password_tries = 0;
968 else
969 {
970 fprintf(stderr, "DEBUG: Get-Printer-Attributes returned %s.\n",
971 ippErrorString(ipp_status));
972
973 if (ipp_status == IPP_PRINTER_BUSY ||
974 ipp_status == IPP_SERVICE_UNAVAILABLE)
975 {
976 if (contimeout && (time(NULL) - start_time) > contimeout)
977 {
978 _cupsLangPrintFilter(stderr, "ERROR",
979 _("The printer is not responding."));
980 return (CUPS_BACKEND_FAILED);
981 }
982
983 _cupsLangPrintFilter(stderr, "INFO", _("The printer is in use."));
984
985 report_printer_state(supported);
986
987 sleep((unsigned)delay);
988
989 delay = _cupsNextDelay(delay, &prev_delay);
990 }
991 else if ((ipp_status == IPP_BAD_REQUEST ||
992 ipp_status == IPP_VERSION_NOT_SUPPORTED) && version > 10)
993 {
994 /*
995 * Switch to IPP/1.1 or IPP/1.0...
996 */
997
998 if (version >= 20)
999 {
1000 _cupsLangPrintFilter(stderr, "INFO", _("Preparing to print."));
1001 fprintf(stderr,
1002 "DEBUG: The printer does not support IPP/%d.%d, trying "
1003 "IPP/1.1.\n", version / 10, version % 10);
1004 version = 11;
1005 }
1006 else
1007 {
1008 _cupsLangPrintFilter(stderr, "INFO", _("Preparing to print."));
1009 fprintf(stderr,
1010 "DEBUG: The printer does not support IPP/%d.%d, trying "
1011 "IPP/1.0.\n", version / 10, version % 10);
1012 version = 10;
1013 }
1014
1015 httpReconnect(http);
1016 }
1017 else if (ipp_status == IPP_NOT_FOUND)
1018 {
1019 _cupsLangPrintFilter(stderr, "ERROR",
1020 _("The printer configuration is incorrect or the "
1021 "printer no longer exists."));
1022
1023 ippDelete(supported);
1024
1025 return (CUPS_BACKEND_STOP);
1026 }
1027 else if (ipp_status == IPP_FORBIDDEN ||
1028 ipp_status == IPP_AUTHENTICATION_CANCELED)
1029 {
1030 const char *www_auth = httpGetField(http, HTTP_FIELD_WWW_AUTHENTICATE);
1031 /* WWW-Authenticate field value */
1032
1033 if (!strncmp(www_auth, "Negotiate", 9))
1034 auth_info_required = "negotiate";
1035 else if (www_auth[0])
1036 auth_info_required = "username,password";
1037
1038 fprintf(stderr, "ATTR: auth-info-required=%s\n", auth_info_required);
1039 return (CUPS_BACKEND_AUTH_REQUIRED);
1040 }
1041 else if (ipp_status != IPP_NOT_AUTHORIZED)
1042 {
1043 _cupsLangPrintFilter(stderr, "ERROR",
1044 _("Unable to get printer status."));
1045 sleep(10);
1046
1047 httpReconnect(http);
1048 }
1049
1050 ippDelete(supported);
1051 supported = NULL;
1052 continue;
1053 }
1054
1055 if (!getenv("CLASS"))
1056 {
1057 /*
1058 * Check printer-is-accepting-jobs = false and printer-state-reasons for the
1059 * "spool-area-full" keyword...
1060 */
1061
1062 int busy = 0;
1063
1064 if ((printer_accepting = ippFindAttribute(supported,
1065 "printer-is-accepting-jobs",
1066 IPP_TAG_BOOLEAN)) != NULL &&
1067 !printer_accepting->values[0].boolean)
1068 busy = 1;
1069 else if (!printer_accepting)
1070 update_reasons(NULL, "+cups-ipp-conformance-failure-report,"
1071 "cups-ipp-missing-printer-is-accepting-jobs");
1072
1073 if ((printer_state = ippFindAttribute(supported,
1074 "printer-state-reasons",
1075 IPP_TAG_KEYWORD)) == NULL)
1076 {
1077 update_reasons(NULL, "+cups-ipp-conformance-failure-report,"
1078 "cups-ipp-missing-printer-state-reasons");
1079 }
1080 else if (!busy)
1081 {
1082 for (i = 0; i < printer_state->num_values; i ++)
1083 {
1084 if (!strcmp(printer_state->values[0].string.text,
1085 "spool-area-full") ||
1086 !strncmp(printer_state->values[0].string.text, "spool-area-full-",
1087 16))
1088 {
1089 busy = 1;
1090 break;
1091 }
1092 }
1093 }
1094
1095 if (busy)
1096 {
1097 _cupsLangPrintFilter(stderr, "INFO", _("The printer is in use."));
1098
1099 report_printer_state(supported);
1100
1101 sleep((unsigned)delay);
1102
1103 delay = _cupsNextDelay(delay, &prev_delay);
1104
1105 ippDelete(supported);
1106 supported = NULL;
1107 ipp_status = IPP_STATUS_ERROR_BUSY;
1108 continue;
1109 }
1110 }
1111
1112 /*
1113 * Check for supported attributes...
1114 */
1115
1116 #ifdef HAVE_LIBZ
1117 if ((compression_sup = ippFindAttribute(supported, "compression-supported",
1118 IPP_TAG_KEYWORD)) != NULL)
1119 {
1120 /*
1121 * Check whether the requested compression is supported and/or default to
1122 * compression if supported...
1123 */
1124
1125 if (compression && !ippContainsString(compression_sup, compression))
1126 {
1127 fprintf(stderr, "DEBUG: Printer does not support the requested "
1128 "compression value \"%s\".\n", compression);
1129 compression = NULL;
1130 }
1131 else if (!compression && (!strcmp(final_content_type, "image/pwg-raster") || !strcmp(final_content_type, "image/urf")))
1132 {
1133 if (ippContainsString(compression_sup, "gzip"))
1134 compression = "gzip";
1135 else if (ippContainsString(compression_sup, "deflate"))
1136 compression = "deflate";
1137
1138 if (compression)
1139 fprintf(stderr, "DEBUG: Automatically using \"%s\" compression.\n",
1140 compression);
1141 }
1142 }
1143 #endif /* HAVE_LIBZ */
1144
1145 if ((copies_sup = ippFindAttribute(supported, "copies-supported",
1146 IPP_TAG_RANGE)) != NULL)
1147 {
1148 /*
1149 * Has the "copies-supported" attribute - does it have an upper
1150 * bound > 1?
1151 */
1152
1153 fprintf(stderr, "DEBUG: copies-supported=%d-%d\n",
1154 copies_sup->values[0].range.lower,
1155 copies_sup->values[0].range.upper);
1156
1157 if (copies_sup->values[0].range.upper <= 1)
1158 copies_sup = NULL; /* No */
1159 }
1160
1161 cups_version = ippFindAttribute(supported, "cups-version", IPP_TAG_TEXT);
1162
1163 encryption_sup = ippFindAttribute(supported, "job-password-encryption-supported", IPP_TAG_KEYWORD);
1164
1165 if ((format_sup = ippFindAttribute(supported, "document-format-supported",
1166 IPP_TAG_MIMETYPE)) != NULL)
1167 {
1168 fprintf(stderr, "DEBUG: document-format-supported (%d values)\n",
1169 format_sup->num_values);
1170 for (i = 0; i < format_sup->num_values; i ++)
1171 fprintf(stderr, "DEBUG: [%d] = \"%s\"\n", i,
1172 format_sup->values[i].string.text);
1173 }
1174
1175 if ((media_col_sup = ippFindAttribute(supported, "media-col-supported",
1176 IPP_TAG_KEYWORD)) != NULL)
1177 {
1178 fprintf(stderr, "DEBUG: media-col-supported (%d values)\n",
1179 media_col_sup->num_values);
1180 for (i = 0; i < media_col_sup->num_values; i ++)
1181 fprintf(stderr, "DEBUG: [%d] = \"%s\"\n", i,
1182 media_col_sup->values[i].string.text);
1183 }
1184
1185 print_color_mode_sup = ippFindAttribute(supported, "print-color-mode-supported", IPP_TAG_KEYWORD);
1186
1187 if ((operations_sup = ippFindAttribute(supported, "operations-supported",
1188 IPP_TAG_ENUM)) != NULL)
1189 {
1190 fprintf(stderr, "DEBUG: operations-supported (%d values)\n",
1191 operations_sup->num_values);
1192 for (i = 0; i < operations_sup->num_values; i ++)
1193 fprintf(stderr, "DEBUG: [%d] = %s\n", i,
1194 ippOpString(operations_sup->values[i].integer));
1195
1196 for (i = 0; i < operations_sup->num_values; i ++)
1197 if (operations_sup->values[i].integer == IPP_PRINT_JOB)
1198 break;
1199
1200 if (i >= operations_sup->num_values)
1201 update_reasons(NULL, "+cups-ipp-conformance-failure-report,"
1202 "cups-ipp-missing-print-job");
1203
1204 for (i = 0; i < operations_sup->num_values; i ++)
1205 if (operations_sup->values[i].integer == IPP_CANCEL_JOB)
1206 break;
1207
1208 if (i >= operations_sup->num_values)
1209 update_reasons(NULL, "+cups-ipp-conformance-failure-report,"
1210 "cups-ipp-missing-cancel-job");
1211
1212 for (i = 0; i < operations_sup->num_values; i ++)
1213 if (operations_sup->values[i].integer == IPP_GET_JOB_ATTRIBUTES)
1214 break;
1215
1216 if (i >= operations_sup->num_values)
1217 update_reasons(NULL, "+cups-ipp-conformance-failure-report,"
1218 "cups-ipp-missing-get-job-attributes");
1219
1220 for (i = 0; i < operations_sup->num_values; i ++)
1221 if (operations_sup->values[i].integer == IPP_GET_PRINTER_ATTRIBUTES)
1222 break;
1223
1224 if (i >= operations_sup->num_values)
1225 update_reasons(NULL, "+cups-ipp-conformance-failure-report,"
1226 "cups-ipp-missing-get-printer-attributes");
1227
1228 for (i = 0; i < operations_sup->num_values; i ++)
1229 {
1230 if (operations_sup->values[i].integer == IPP_VALIDATE_JOB)
1231 validate_job = 1;
1232 else if (operations_sup->values[i].integer == IPP_CREATE_JOB)
1233 create_job = 1;
1234 else if (operations_sup->values[i].integer == IPP_SEND_DOCUMENT)
1235 send_document = 1;
1236 else if (operations_sup->values[i].integer == IPP_GET_JOB_ATTRIBUTES)
1237 get_job_attrs = 1;
1238 }
1239
1240 if (create_job && !send_document)
1241 {
1242 fputs("DEBUG: Printer supports Create-Job but not Send-Document.\n",
1243 stderr);
1244 create_job = 0;
1245
1246 update_reasons(NULL, "+cups-ipp-conformance-failure-report,"
1247 "cups-ipp-missing-send-document");
1248 }
1249
1250 if (!validate_job)
1251 update_reasons(NULL, "+cups-ipp-conformance-failure-report,"
1252 "cups-ipp-missing-validate-job");
1253 }
1254 else
1255 update_reasons(NULL, "+cups-ipp-conformance-failure-report,"
1256 "cups-ipp-missing-operations-supported");
1257
1258 doc_handling_sup = ippFindAttribute(supported,
1259 "multiple-document-handling-supported",
1260 IPP_TAG_KEYWORD);
1261
1262 report_printer_state(supported);
1263 }
1264 while (!job_canceled && ipp_status > IPP_OK_CONFLICT);
1265
1266 if (job_canceled)
1267 return (CUPS_BACKEND_OK);
1268
1269 /*
1270 * See if the printer is accepting jobs and is not stopped; if either
1271 * condition is true and we are printing to a class, requeue the job...
1272 */
1273
1274 if (getenv("CLASS") != NULL)
1275 {
1276 printer_state = ippFindAttribute(supported, "printer-state",
1277 IPP_TAG_ENUM);
1278 printer_accepting = ippFindAttribute(supported, "printer-is-accepting-jobs",
1279 IPP_TAG_BOOLEAN);
1280
1281 if (printer_state == NULL ||
1282 (printer_state->values[0].integer > IPP_PRINTER_PROCESSING &&
1283 waitprinter) ||
1284 printer_accepting == NULL ||
1285 !printer_accepting->values[0].boolean)
1286 {
1287 /*
1288 * If the CLASS environment variable is set, the job was submitted
1289 * to a class and not to a specific queue. In this case, we want
1290 * to abort immediately so that the job can be requeued on the next
1291 * available printer in the class.
1292 */
1293
1294 _cupsLangPrintFilter(stderr, "INFO",
1295 _("Unable to contact printer, queuing on next "
1296 "printer in class."));
1297
1298 ippDelete(supported);
1299 httpClose(http);
1300
1301 /*
1302 * Sleep 5 seconds to keep the job from requeuing too rapidly...
1303 */
1304
1305 sleep(5);
1306
1307 return (CUPS_BACKEND_FAILED);
1308 }
1309 }
1310
1311 /*
1312 * See if the printer supports multiple copies...
1313 */
1314
1315 copies = atoi(argv[4]);
1316
1317 if (copies_sup || argc < 7)
1318 copies_remaining = 1;
1319 else
1320 copies_remaining = copies;
1321
1322 /*
1323 * Prepare remaining printing options...
1324 */
1325
1326 options = NULL;
1327
1328 if (send_options)
1329 {
1330 num_options = cupsParseOptions(argv[5], 0, &options);
1331
1332 if (!cups_version && media_col_sup)
1333 {
1334 /*
1335 * Load the PPD file and generate PWG attribute mapping information...
1336 */
1337
1338 ppd_attr_t *mandatory; /* cupsMandatory value */
1339
1340 ppd = ppdOpenFile(getenv("PPD"));
1341 pc = _ppdCacheCreateWithPPD(ppd);
1342
1343 ppdMarkDefaults(ppd);
1344 cupsMarkOptions(ppd, num_options, options);
1345
1346 if ((mandatory = ppdFindAttr(ppd, "cupsMandatory", NULL)) != NULL)
1347 strlcpy(mandatory_attrs, mandatory->value, sizeof(mandatory_attrs));
1348 }
1349
1350 /*
1351 * Validate job-password/-encryption...
1352 */
1353
1354 if (cupsGetOption("job-password", num_options, options))
1355 {
1356 const char *keyword; /* job-password-encryption value */
1357 static const char * const hashes[] =
1358 { /* List of supported hash algorithms, in order of preference */
1359 "sha-512",
1360 "sha-384",
1361 "sha-512_256",
1362 "sha-512-224",
1363 "sha-256",
1364 "sha-224",
1365 "sha",
1366 "none"
1367 };
1368
1369 if ((keyword = cupsGetOption("job-password-encryption", num_options, options)) == NULL || !ippContainsString(encryption_sup, keyword))
1370 {
1371 /*
1372 * Either no job-password-encryption or the value isn't supported by
1373 * the printer...
1374 */
1375
1376 for (i = 0; i < (int)(sizeof(hashes) / sizeof(hashes[0])); i ++)
1377 if (ippContainsString(encryption_sup, hashes[i]))
1378 break;
1379
1380 if (i < (int)(sizeof(hashes) / sizeof(hashes[0])))
1381 num_options = cupsAddOption("job-password-encryption", hashes[i], num_options, &options);
1382 }
1383 }
1384 }
1385 else
1386 num_options = 0;
1387
1388 document_format = NULL;
1389
1390 if (format_sup != NULL)
1391 {
1392 if (ippContainsString(format_sup, final_content_type))
1393 document_format = final_content_type;
1394 else if (ippContainsString(format_sup, "application/octet-stream"))
1395 document_format = "application/octet-stream";
1396 }
1397
1398 fprintf(stderr, "DEBUG: final_content_type=\"%s\", document_format=\"%s\"\n",
1399 final_content_type, document_format ? document_format : "(null)");
1400
1401 /*
1402 * If the printer does not support HTTP/1.1 (which IPP requires), copy stdin
1403 * to a temporary file so that we can do a HTTP/1.0 submission...
1404 *
1405 * (I hate compatibility hacks!)
1406 */
1407
1408 if (http->version < HTTP_1_1 && num_files == 0)
1409 {
1410 if ((fd = cupsTempFd(tmpfilename, sizeof(tmpfilename))) < 0)
1411 {
1412 perror("DEBUG: Unable to create temporary file");
1413 return (CUPS_BACKEND_FAILED);
1414 }
1415
1416 _cupsLangPrintFilter(stderr, "INFO", _("Copying print data."));
1417
1418 if ((compatsize = write(fd, buffer, (size_t)bytes)) < 0)
1419 {
1420 perror("DEBUG: Unable to write temporary file");
1421 return (CUPS_BACKEND_FAILED);
1422 }
1423
1424 if ((bytes = backendRunLoop(-1, fd, snmp_fd, &(addrlist->addr), 0, 0,
1425 backendNetworkSideCB)) < 0)
1426 return (CUPS_BACKEND_FAILED);
1427
1428 compatsize += bytes;
1429
1430 close(fd);
1431
1432 compatfile = tmpfilename;
1433 files = &compatfile;
1434 num_files = 1;
1435 }
1436 else if (http->version < HTTP_1_1 && num_files == 1)
1437 {
1438 struct stat fileinfo; /* File information */
1439
1440 if (!stat(files[0], &fileinfo))
1441 compatsize = fileinfo.st_size;
1442 }
1443
1444 /*
1445 * If the printer only claims to support IPP/1.0, or if the user specifically
1446 * included version=1.0 in the URI, then do not try to use Create-Job or
1447 * Send-Document. This is another dreaded compatibility hack, but
1448 * unfortunately there are enough broken printers out there that we need
1449 * this for now...
1450 */
1451
1452 if (version == 10)
1453 create_job = send_document = 0;
1454
1455 /*
1456 * Start monitoring the printer in the background...
1457 */
1458
1459 monitor.uri = uri;
1460 monitor.hostname = hostname;
1461 monitor.user = argv[2];
1462 monitor.resource = resource;
1463 monitor.port = port;
1464 monitor.version = version;
1465 monitor.job_id = 0;
1466 monitor.create_job = create_job;
1467 monitor.get_job_attrs = get_job_attrs;
1468 monitor.encryption = cupsEncryption();
1469 monitor.job_state = IPP_JOB_PENDING;
1470 monitor.printer_state = IPP_PRINTER_IDLE;
1471
1472 if (create_job)
1473 {
1474 monitor.job_name = argv[3];
1475 }
1476 else
1477 {
1478 snprintf(print_job_name, sizeof(print_job_name), "%s - %s", argv[1],
1479 argv[3]);
1480 monitor.job_name = print_job_name;
1481 }
1482
1483 _cupsThreadCreate((_cups_thread_func_t)monitor_printer, &monitor);
1484
1485 /*
1486 * Validate access to the printer...
1487 */
1488
1489 while (!job_canceled && validate_job)
1490 {
1491 request = new_request(IPP_VALIDATE_JOB, version, uri, argv[2],
1492 monitor.job_name, num_options, options, compression,
1493 copies_sup ? copies : 1, document_format, pc, ppd,
1494 media_col_sup, doc_handling_sup, print_color_mode_sup);
1495
1496 response = cupsDoRequest(http, request, resource);
1497
1498 ipp_status = cupsLastError();
1499
1500 fprintf(stderr, "DEBUG: Validate-Job: %s (%s)\n",
1501 ippErrorString(ipp_status), cupsLastErrorString());
1502
1503 if ((job_auth = ippFindAttribute(response, "job-authorization-uri",
1504 IPP_TAG_URI)) != NULL)
1505 num_options = cupsAddOption("job-authorization-uri",
1506 ippGetString(job_auth, 0, NULL), num_options,
1507 &options);
1508
1509 ippDelete(response);
1510
1511 if (job_canceled)
1512 break;
1513
1514 if (ipp_status == IPP_STATUS_ERROR_SERVICE_UNAVAILABLE ||
1515 ipp_status == IPP_STATUS_ERROR_BUSY)
1516 {
1517 _cupsLangPrintFilter(stderr, "INFO", _("The printer is in use."));
1518 sleep(10);
1519 }
1520 else if (ipp_status == IPP_STATUS_ERROR_DOCUMENT_FORMAT_NOT_SUPPORTED ||
1521 ipp_status == IPP_STATUS_ERROR_ATTRIBUTES_OR_VALUES ||
1522 ipp_status == IPP_STATUS_ERROR_CUPS_ACCOUNT_INFO_NEEDED ||
1523 ipp_status == IPP_STATUS_ERROR_CUPS_ACCOUNT_CLOSED ||
1524 ipp_status == IPP_STATUS_ERROR_CUPS_ACCOUNT_LIMIT_REACHED ||
1525 ipp_status == IPP_STATUS_ERROR_CUPS_ACCOUNT_AUTHORIZATION_FAILED)
1526 goto cleanup;
1527 else if (ipp_status == IPP_STATUS_ERROR_FORBIDDEN ||
1528 ipp_status == IPP_STATUS_ERROR_NOT_AUTHORIZED ||
1529 ipp_status == IPP_STATUS_ERROR_CUPS_AUTHENTICATION_CANCELED)
1530 {
1531 const char *www_auth = httpGetField(http, HTTP_FIELD_WWW_AUTHENTICATE);
1532 /* WWW-Authenticate field value */
1533
1534 if (!strncmp(www_auth, "Negotiate", 9))
1535 auth_info_required = "negotiate";
1536 else if (www_auth[0])
1537 auth_info_required = "username,password";
1538
1539 goto cleanup;
1540 }
1541 else if (ipp_status == IPP_STATUS_ERROR_OPERATION_NOT_SUPPORTED)
1542 {
1543 /*
1544 * This is all too common...
1545 */
1546
1547 update_reasons(NULL, "+cups-ipp-conformance-failure-report,"
1548 "cups-ipp-missing-validate-job");
1549 break;
1550 }
1551 else if (ipp_status < IPP_REDIRECTION_OTHER_SITE ||
1552 ipp_status == IPP_BAD_REQUEST)
1553 break;
1554 }
1555
1556 /*
1557 * Then issue the print-job request...
1558 */
1559
1560 job_id = 0;
1561
1562 while (!job_canceled && copies_remaining > 0)
1563 {
1564 /*
1565 * Check for side-channel requests...
1566 */
1567
1568 backendCheckSideChannel(snmp_fd, http->hostaddr);
1569
1570 /*
1571 * Build the IPP job creation request...
1572 */
1573
1574 if (job_canceled)
1575 break;
1576
1577 request = new_request((num_files > 1 || create_job) ? IPP_CREATE_JOB :
1578 IPP_PRINT_JOB,
1579 version, uri, argv[2], monitor.job_name, num_options,
1580 options, compression, copies_sup ? copies : 1,
1581 document_format, pc, ppd, media_col_sup,
1582 doc_handling_sup, print_color_mode_sup);
1583
1584 /*
1585 * Do the request...
1586 */
1587
1588 if (num_files > 1 || create_job)
1589 response = cupsDoRequest(http, request, resource);
1590 else
1591 {
1592 size_t length = 0; /* Length of request */
1593
1594 if (compatsize > 0)
1595 {
1596 fputs("DEBUG: Sending file using HTTP/1.0 Content-Length...\n", stderr);
1597 length = ippLength(request) + (size_t)compatsize;
1598 }
1599 else
1600 fputs("DEBUG: Sending file using HTTP/1.1 chunking...\n", stderr);
1601
1602 http_status = cupsSendRequest(http, request, resource, length);
1603 if (http_status == HTTP_CONTINUE && request->state == IPP_DATA)
1604 {
1605 if (compression && strcmp(compression, "none"))
1606 httpSetField(http, HTTP_FIELD_CONTENT_ENCODING, compression);
1607
1608 if (num_files == 1)
1609 {
1610 if ((fd = open(files[0], O_RDONLY)) < 0)
1611 {
1612 _cupsLangPrintError("ERROR", _("Unable to open print file"));
1613 return (CUPS_BACKEND_FAILED);
1614 }
1615 }
1616 else
1617 {
1618 fd = 0;
1619 http_status = cupsWriteRequestData(http, buffer, (size_t)bytes);
1620 }
1621
1622 while (http_status == HTTP_CONTINUE &&
1623 (!job_canceled || compatsize > 0))
1624 {
1625 /*
1626 * Check for side-channel requests and more print data...
1627 */
1628
1629 FD_ZERO(&input);
1630 FD_SET(fd, &input);
1631 FD_SET(snmp_fd, &input);
1632 FD_SET(CUPS_SC_FD, &input);
1633
1634 while (select(fd > snmp_fd ? fd + 1 : snmp_fd + 1, &input, NULL, NULL,
1635 NULL) <= 0 && !job_canceled);
1636
1637 if (FD_ISSET(snmp_fd, &input))
1638 backendCheckSideChannel(snmp_fd, http->hostaddr);
1639
1640 if (FD_ISSET(fd, &input))
1641 {
1642 if ((bytes = read(fd, buffer, sizeof(buffer))) > 0)
1643 {
1644 fprintf(stderr, "DEBUG: Read %d bytes...\n", (int)bytes);
1645
1646 if ((http_status = cupsWriteRequestData(http, buffer, (size_t)bytes))
1647 != HTTP_CONTINUE)
1648 break;
1649 }
1650 else if (bytes == 0 || (errno != EINTR && errno != EAGAIN))
1651 break;
1652 }
1653 }
1654
1655 if (http_status == HTTP_ERROR)
1656 fprintf(stderr, "DEBUG: Error writing document data for "
1657 "Print-Job: %s\n", strerror(httpError(http)));
1658
1659 if (num_files == 1)
1660 close(fd);
1661 }
1662
1663 response = cupsGetResponse(http, resource);
1664 ippDelete(request);
1665 }
1666
1667 ipp_status = cupsLastError();
1668
1669 fprintf(stderr, "DEBUG: %s: %s (%s)\n",
1670 (num_files > 1 || create_job) ? "Create-Job" : "Print-Job",
1671 ippErrorString(ipp_status), cupsLastErrorString());
1672
1673 if (ipp_status > IPP_OK_CONFLICT)
1674 {
1675 job_id = 0;
1676
1677 if (job_canceled)
1678 break;
1679
1680 if (ipp_status == IPP_STATUS_ERROR_SERVICE_UNAVAILABLE ||
1681 ipp_status == IPP_STATUS_ERROR_NOT_POSSIBLE ||
1682 ipp_status == IPP_STATUS_ERROR_BUSY)
1683 {
1684 _cupsLangPrintFilter(stderr, "INFO", _("The printer is in use."));
1685 sleep(10);
1686
1687 if (num_files == 0)
1688 {
1689 /*
1690 * We can't re-submit when we have no files to print, so exit
1691 * immediately with the right status code...
1692 */
1693
1694 goto cleanup;
1695 }
1696 }
1697 else if (ipp_status == IPP_STATUS_ERROR_JOB_CANCELED ||
1698 ipp_status == IPP_STATUS_ERROR_NOT_AUTHORIZED ||
1699 ipp_status == IPP_STATUS_ERROR_ATTRIBUTES_OR_VALUES ||
1700 ipp_status == IPP_STATUS_ERROR_CUPS_ACCOUNT_INFO_NEEDED ||
1701 ipp_status == IPP_STATUS_ERROR_CUPS_ACCOUNT_CLOSED ||
1702 ipp_status == IPP_STATUS_ERROR_CUPS_ACCOUNT_LIMIT_REACHED ||
1703 ipp_status == IPP_STATUS_ERROR_CUPS_ACCOUNT_AUTHORIZATION_FAILED)
1704 goto cleanup;
1705 else
1706 {
1707 /*
1708 * Update auth-info-required as needed...
1709 */
1710
1711 _cupsLangPrintFilter(stderr, "ERROR",
1712 _("Print job was not accepted."));
1713
1714 if (ipp_status == IPP_STATUS_ERROR_FORBIDDEN ||
1715 ipp_status == IPP_STATUS_ERROR_CUPS_AUTHENTICATION_CANCELED)
1716 {
1717 const char *www_auth = httpGetField(http, HTTP_FIELD_WWW_AUTHENTICATE);
1718 /* WWW-Authenticate field value */
1719
1720 if (!strncmp(www_auth, "Negotiate", 9))
1721 auth_info_required = "negotiate";
1722 else if (www_auth[0])
1723 auth_info_required = "username,password";
1724 }
1725 else if (ipp_status == IPP_REQUEST_VALUE)
1726 {
1727 /*
1728 * Print file is too large, abort this job...
1729 */
1730
1731 goto cleanup;
1732 }
1733 else
1734 sleep(10);
1735
1736 if (num_files == 0)
1737 {
1738 /*
1739 * We can't re-submit when we have no files to print, so exit
1740 * immediately with the right status code...
1741 */
1742
1743 goto cleanup;
1744 }
1745 }
1746 }
1747 else if ((job_id_attr = ippFindAttribute(response, "job-id",
1748 IPP_TAG_INTEGER)) == NULL)
1749 {
1750 fputs("DEBUG: Print job accepted - job ID unknown.\n", stderr);
1751 update_reasons(NULL, "+cups-ipp-conformance-failure-report,"
1752 "cups-ipp-missing-job-id");
1753 job_id = 0;
1754 }
1755 else
1756 {
1757 password_tries = 0;
1758 monitor.job_id = job_id = job_id_attr->values[0].integer;
1759 fprintf(stderr, "DEBUG: Print job accepted - job ID %d.\n", job_id);
1760 }
1761
1762 ippDelete(response);
1763
1764 if (job_canceled)
1765 break;
1766
1767 if (job_id && (num_files > 1 || create_job))
1768 {
1769 for (i = 0; num_files == 0 || i < num_files; i ++)
1770 {
1771 /*
1772 * Check for side-channel requests...
1773 */
1774
1775 backendCheckSideChannel(snmp_fd, http->hostaddr);
1776
1777 /*
1778 * Send the next file in the job...
1779 */
1780
1781 request = ippNewRequest(IPP_SEND_DOCUMENT);
1782 ippSetVersion(request, version / 10, version % 10);
1783
1784 ippAddString(request, IPP_TAG_OPERATION, IPP_TAG_URI, "printer-uri",
1785 NULL, uri);
1786
1787 ippAddInteger(request, IPP_TAG_OPERATION, IPP_TAG_INTEGER, "job-id",
1788 job_id);
1789
1790 if (argv[2][0])
1791 ippAddString(request, IPP_TAG_OPERATION, IPP_TAG_NAME,
1792 "requesting-user-name", NULL, argv[2]);
1793
1794 ippAddBoolean(request, IPP_TAG_OPERATION, "last-document",
1795 (i + 1) >= num_files);
1796
1797 if (document_format)
1798 ippAddString(request, IPP_TAG_OPERATION, IPP_TAG_MIMETYPE,
1799 "document-format", NULL, document_format);
1800
1801 if (compression)
1802 ippAddString(request, IPP_TAG_OPERATION, IPP_TAG_KEYWORD,
1803 "compression", NULL, compression);
1804
1805 fprintf(stderr, "DEBUG: Sending file %d using chunking...\n", i + 1);
1806 http_status = cupsSendRequest(http, request, resource, 0);
1807 if (http_status == HTTP_CONTINUE && request->state == IPP_DATA)
1808 {
1809 if (compression && strcmp(compression, "none"))
1810 httpSetField(http, HTTP_FIELD_CONTENT_ENCODING, compression);
1811
1812 if (num_files == 0)
1813 {
1814 fd = 0;
1815 http_status = cupsWriteRequestData(http, buffer, (size_t)bytes);
1816 }
1817 else
1818 {
1819 if ((fd = open(files[i], O_RDONLY)) < 0)
1820 {
1821 _cupsLangPrintError("ERROR", _("Unable to open print file"));
1822 return (CUPS_BACKEND_FAILED);
1823 }
1824 }
1825 }
1826 else
1827 fd = -1;
1828
1829 if (fd >= 0)
1830 {
1831 while (!job_canceled && http_status == HTTP_CONTINUE &&
1832 (bytes = read(fd, buffer, sizeof(buffer))) > 0)
1833 {
1834 if ((http_status = cupsWriteRequestData(http, buffer, (size_t)bytes))
1835 != HTTP_CONTINUE)
1836 break;
1837 else
1838 {
1839 /*
1840 * Check for side-channel requests...
1841 */
1842
1843 backendCheckSideChannel(snmp_fd, http->hostaddr);
1844 }
1845 }
1846
1847 if (fd > 0)
1848 close(fd);
1849 }
1850
1851 if (http_status == HTTP_ERROR)
1852 fprintf(stderr, "DEBUG: Error writing document data for "
1853 "Send-Document: %s\n", strerror(httpError(http)));
1854
1855 ippDelete(cupsGetResponse(http, resource));
1856 ippDelete(request);
1857
1858 fprintf(stderr, "DEBUG: Send-Document: %s (%s)\n",
1859 ippErrorString(cupsLastError()), cupsLastErrorString());
1860
1861 if (cupsLastError() > IPP_OK_CONFLICT && !job_canceled)
1862 {
1863 ipp_status = cupsLastError();
1864
1865 _cupsLangPrintFilter(stderr, "ERROR",
1866 _("Unable to add document to print job."));
1867 break;
1868 }
1869 else
1870 {
1871 password_tries = 0;
1872
1873 if (num_files == 0 || fd < 0)
1874 break;
1875 }
1876 }
1877 }
1878
1879 if (job_canceled)
1880 break;
1881
1882 if (ipp_status <= IPP_OK_CONFLICT && argc > 6)
1883 {
1884 fprintf(stderr, "PAGE: 1 %d\n", copies_sup ? atoi(argv[4]) : 1);
1885 copies_remaining --;
1886 }
1887 else if ((ipp_status == IPP_STATUS_ERROR_DOCUMENT_FORMAT_ERROR || ipp_status == IPP_STATUS_ERROR_DOCUMENT_UNPRINTABLE) &&
1888 argc == 6 &&
1889 document_format && strcmp(document_format, "image/pwg-raster") && strcmp(document_format, "image/urf"))
1890 {
1891 /*
1892 * Need to reprocess the job as raster...
1893 */
1894
1895 fputs("JOBSTATE: cups-retry-as-raster\n", stderr);
1896 if (job_id > 0)
1897 cancel_job(http, uri, job_id, resource, argv[2], version);
1898
1899 goto cleanup;
1900 }
1901 else if (ipp_status == IPP_SERVICE_UNAVAILABLE ||
1902 ipp_status == IPP_NOT_POSSIBLE ||
1903 ipp_status == IPP_PRINTER_BUSY)
1904 {
1905 if (argc == 6)
1906 {
1907 /*
1908 * Need to reprocess the entire job; if we have a job ID, cancel the
1909 * job first...
1910 */
1911
1912 if (job_id > 0)
1913 cancel_job(http, uri, job_id, resource, argv[2], version);
1914
1915 goto cleanup;
1916 }
1917 continue;
1918 }
1919 else if (ipp_status == IPP_REQUEST_VALUE ||
1920 ipp_status == IPP_ERROR_JOB_CANCELED ||
1921 ipp_status == IPP_NOT_AUTHORIZED ||
1922 ipp_status == IPP_STATUS_ERROR_CUPS_ACCOUNT_INFO_NEEDED ||
1923 ipp_status == IPP_STATUS_ERROR_CUPS_ACCOUNT_CLOSED ||
1924 ipp_status == IPP_STATUS_ERROR_CUPS_ACCOUNT_LIMIT_REACHED ||
1925 ipp_status == IPP_STATUS_ERROR_CUPS_ACCOUNT_AUTHORIZATION_FAILED ||
1926 ipp_status == IPP_INTERNAL_ERROR)
1927 {
1928 /*
1929 * Print file is too large, job was canceled, we need new
1930 * authentication data, or we had some sort of error...
1931 */
1932
1933 goto cleanup;
1934 }
1935 else if (ipp_status == IPP_STATUS_ERROR_CUPS_UPGRADE_REQUIRED)
1936 {
1937 /*
1938 * Server is configured incorrectly; the policy for Create-Job and
1939 * Send-Document has to be the same (auth or no auth, encryption or
1940 * no encryption). Force the queue to stop since printing will never
1941 * work.
1942 */
1943
1944 fputs("DEBUG: The server or printer is configured incorrectly.\n",
1945 stderr);
1946 fputs("DEBUG: The policy for Create-Job and Send-Document must have the "
1947 "same authentication and encryption requirements.\n", stderr);
1948
1949 ipp_status = IPP_STATUS_ERROR_INTERNAL;
1950
1951 if (job_id > 0)
1952 cancel_job(http, uri, job_id, resource, argv[2], version);
1953
1954 goto cleanup;
1955 }
1956 else if (ipp_status == IPP_NOT_FOUND)
1957 {
1958 /*
1959 * Printer does not actually implement support for Create-Job/
1960 * Send-Document, so log the conformance issue and stop the printer.
1961 */
1962
1963 fputs("DEBUG: This printer claims to support Create-Job and "
1964 "Send-Document, but those operations failed.\n", stderr);
1965 fputs("DEBUG: Add '?version=1.0' to the device URI to use legacy "
1966 "compatibility mode.\n", stderr);
1967 update_reasons(NULL, "+cups-ipp-conformance-failure-report,"
1968 "cups-ipp-missing-send-document");
1969
1970 ipp_status = IPP_INTERNAL_ERROR; /* Force queue to stop */
1971
1972 goto cleanup;
1973 }
1974 else
1975 copies_remaining --;
1976
1977 /*
1978 * Wait for the job to complete...
1979 */
1980
1981 if (!job_id || !waitjob || !get_job_attrs)
1982 continue;
1983
1984 fputs("STATE: +cups-waiting-for-job-completed\n", stderr);
1985
1986 _cupsLangPrintFilter(stderr, "INFO", _("Waiting for job to complete."));
1987
1988 for (delay = _cupsNextDelay(0, &prev_delay), waittime = time(NULL) + 30; !job_canceled;)
1989 {
1990 /*
1991 * Check for side-channel requests...
1992 */
1993
1994 backendCheckSideChannel(snmp_fd, http->hostaddr);
1995
1996 /*
1997 * Check printer state...
1998 */
1999
2000 check_printer_state(http, uri, resource, argv[2], version);
2001
2002 if (cupsLastError() <= IPP_OK_CONFLICT)
2003 password_tries = 0;
2004
2005 /*
2006 * Build an IPP_GET_JOB_ATTRIBUTES request...
2007 */
2008
2009 request = ippNewRequest(IPP_GET_JOB_ATTRIBUTES);
2010 ippSetVersion(request, version / 10, version % 10);
2011
2012 ippAddString(request, IPP_TAG_OPERATION, IPP_TAG_URI, "printer-uri",
2013 NULL, uri);
2014
2015 ippAddInteger(request, IPP_TAG_OPERATION, IPP_TAG_INTEGER, "job-id",
2016 job_id);
2017
2018 if (argv[2][0])
2019 ippAddString(request, IPP_TAG_OPERATION, IPP_TAG_NAME,
2020 "requesting-user-name", NULL, argv[2]);
2021
2022 ippAddStrings(request, IPP_TAG_OPERATION, IPP_TAG_KEYWORD,
2023 "requested-attributes", sizeof(jattrs) / sizeof(jattrs[0]),
2024 NULL, jattrs);
2025
2026 /*
2027 * Do the request...
2028 */
2029
2030 httpReconnect(http);
2031 response = cupsDoRequest(http, request, resource);
2032 ipp_status = cupsLastError();
2033
2034 if (ipp_status == IPP_NOT_FOUND || ipp_status == IPP_NOT_POSSIBLE)
2035 {
2036 /*
2037 * Job has gone away and/or the server has no job history...
2038 */
2039
2040 update_reasons(NULL, "+cups-ipp-conformance-failure-report,"
2041 "cups-ipp-missing-job-history");
2042 ippDelete(response);
2043
2044 ipp_status = IPP_OK;
2045 break;
2046 }
2047
2048 fprintf(stderr, "DEBUG: Get-Job-Attributes: %s (%s)\n",
2049 ippErrorString(ipp_status), cupsLastErrorString());
2050
2051 if (ipp_status <= IPP_OK_CONFLICT)
2052 password_tries = 0;
2053 else
2054 {
2055 if (ipp_status != IPP_SERVICE_UNAVAILABLE &&
2056 ipp_status != IPP_PRINTER_BUSY)
2057 {
2058 ippDelete(response);
2059 ipp_status = IPP_OK;
2060 break;
2061 }
2062 else if (ipp_status == IPP_INTERNAL_ERROR)
2063 {
2064 waitjob_tries ++;
2065
2066 if (waitjob_tries > 4)
2067 {
2068 ippDelete(response);
2069 ipp_status = IPP_OK;
2070 break;
2071 }
2072 }
2073 }
2074
2075 if (response)
2076 {
2077 if ((job_state = ippFindAttribute(response, "job-state",
2078 IPP_TAG_ENUM)) != NULL)
2079 {
2080 /*
2081 * Reflect the remote job state in the local queue...
2082 */
2083
2084 if (cups_version &&
2085 job_state->values[0].integer >= IPP_JOB_PENDING &&
2086 job_state->values[0].integer <= IPP_JOB_COMPLETED)
2087 update_reasons(NULL,
2088 remote_job_states[job_state->values[0].integer -
2089 IPP_JOB_PENDING]);
2090
2091 if ((job_sheets = ippFindAttribute(response, "job-impressions-completed", IPP_TAG_INTEGER)) == NULL)
2092 job_sheets = ippFindAttribute(response, "job-media-sheets-completed", IPP_TAG_INTEGER);
2093
2094 if (job_sheets)
2095 fprintf(stderr, "PAGE: total %d\n",
2096 job_sheets->values[0].integer);
2097
2098 /*
2099 * Stop polling if the job is finished or pending-held for 30 seconds...
2100 */
2101
2102 if (job_state->values[0].integer > IPP_JSTATE_STOPPED ||
2103 (job_state->values[0].integer == IPP_JSTATE_HELD && time(NULL) > waittime))
2104 {
2105 ippDelete(response);
2106 break;
2107 }
2108 }
2109 else if (ipp_status != IPP_SERVICE_UNAVAILABLE &&
2110 ipp_status != IPP_NOT_POSSIBLE &&
2111 ipp_status != IPP_PRINTER_BUSY)
2112 {
2113 /*
2114 * If the printer does not return a job-state attribute, it does not
2115 * conform to the IPP specification - break out immediately and fail
2116 * the job...
2117 */
2118
2119 update_reasons(NULL, "+cups-ipp-conformance-failure-report,"
2120 "cups-ipp-missing-job-state");
2121 ipp_status = IPP_INTERNAL_ERROR;
2122 break;
2123 }
2124 }
2125
2126 ippDelete(response);
2127
2128 /*
2129 * Wait before polling again...
2130 */
2131
2132 sleep((unsigned)delay);
2133
2134 delay = _cupsNextDelay(delay, &prev_delay);
2135 }
2136 }
2137
2138 /*
2139 * Cancel the job as needed...
2140 */
2141
2142 if (job_canceled > 0 && job_id > 0)
2143 {
2144 cancel_job(http, uri, job_id, resource, argv[2], version);
2145
2146 if (cupsLastError() > IPP_OK_CONFLICT)
2147 _cupsLangPrintFilter(stderr, "ERROR", _("Unable to cancel print job."));
2148 }
2149
2150 /*
2151 * Check the printer state and report it if necessary...
2152 */
2153
2154 check_printer_state(http, uri, resource, argv[2], version);
2155
2156 if (cupsLastError() <= IPP_OK_CONFLICT)
2157 password_tries = 0;
2158
2159 /*
2160 * Collect the final page count as needed...
2161 */
2162
2163 if (have_supplies &&
2164 !backendSNMPSupplies(snmp_fd, &(http->addrlist->addr), &page_count,
2165 NULL) &&
2166 page_count > start_count)
2167 fprintf(stderr, "PAGE: total %d\n", page_count - start_count);
2168
2169 #ifdef HAVE_GSSAPI
2170 /*
2171 * See if we used Kerberos at all...
2172 */
2173
2174 if (http->gssctx)
2175 auth_info_required = "negotiate";
2176 #endif /* HAVE_GSSAPI */
2177
2178 /*
2179 * Free memory...
2180 */
2181
2182 cleanup:
2183
2184 cupsFreeOptions(num_options, options);
2185 _ppdCacheDestroy(pc);
2186 ppdClose(ppd);
2187
2188 httpClose(http);
2189
2190 ippDelete(supported);
2191
2192 /*
2193 * Remove the temporary file(s) if necessary...
2194 */
2195
2196 if (tmpfilename[0])
2197 unlink(tmpfilename);
2198
2199 /*
2200 * Return the queue status...
2201 */
2202
2203 if (ipp_status == IPP_NOT_AUTHORIZED || ipp_status == IPP_FORBIDDEN ||
2204 ipp_status == IPP_AUTHENTICATION_CANCELED ||
2205 ipp_status <= IPP_OK_CONFLICT)
2206 fprintf(stderr, "ATTR: auth-info-required=%s\n", auth_info_required);
2207
2208 if (ipp_status == IPP_STATUS_ERROR_CUPS_ACCOUNT_INFO_NEEDED)
2209 fputs("JOBSTATE: account-info-needed\n", stderr);
2210 else if (ipp_status == IPP_STATUS_ERROR_CUPS_ACCOUNT_CLOSED)
2211 fputs("JOBSTATE: account-closed\n", stderr);
2212 else if (ipp_status == IPP_STATUS_ERROR_CUPS_ACCOUNT_LIMIT_REACHED)
2213 fputs("JOBSTATE: account-limit-reached\n", stderr);
2214 else if (ipp_status == IPP_STATUS_ERROR_CUPS_ACCOUNT_AUTHORIZATION_FAILED)
2215 fputs("JOBSTATE: account-authorization-failed\n", stderr);
2216
2217 if (ipp_status == IPP_NOT_AUTHORIZED || ipp_status == IPP_FORBIDDEN ||
2218 ipp_status == IPP_AUTHENTICATION_CANCELED)
2219 return (CUPS_BACKEND_AUTH_REQUIRED);
2220 else if (ipp_status == IPP_STATUS_ERROR_CUPS_ACCOUNT_LIMIT_REACHED ||
2221 ipp_status == IPP_STATUS_ERROR_CUPS_ACCOUNT_INFO_NEEDED ||
2222 ipp_status == IPP_STATUS_ERROR_CUPS_ACCOUNT_CLOSED ||
2223 ipp_status == IPP_STATUS_ERROR_CUPS_ACCOUNT_AUTHORIZATION_FAILED)
2224 return (CUPS_BACKEND_HOLD);
2225 else if (ipp_status == IPP_INTERNAL_ERROR)
2226 return (CUPS_BACKEND_STOP);
2227 else if (ipp_status == IPP_CONFLICT)
2228 return (CUPS_BACKEND_FAILED);
2229 else if (ipp_status == IPP_REQUEST_VALUE ||
2230 ipp_status == IPP_STATUS_ERROR_ATTRIBUTES_OR_VALUES ||
2231 ipp_status == IPP_DOCUMENT_FORMAT || job_canceled < 0)
2232 {
2233 if (ipp_status == IPP_REQUEST_VALUE)
2234 _cupsLangPrintFilter(stderr, "ERROR", _("Print job too large."));
2235 else if (ipp_status == IPP_DOCUMENT_FORMAT)
2236 _cupsLangPrintFilter(stderr, "ERROR",
2237 _("Printer cannot print supplied content."));
2238 else if (ipp_status == IPP_STATUS_ERROR_ATTRIBUTES_OR_VALUES)
2239 _cupsLangPrintFilter(stderr, "ERROR",
2240 _("Printer cannot print with supplied options."));
2241 else
2242 _cupsLangPrintFilter(stderr, "ERROR", _("Print job canceled at printer."));
2243
2244 return (CUPS_BACKEND_CANCEL);
2245 }
2246 else if (ipp_status > IPP_OK_CONFLICT && ipp_status != IPP_ERROR_JOB_CANCELED)
2247 return (CUPS_BACKEND_RETRY_CURRENT);
2248 else
2249 return (CUPS_BACKEND_OK);
2250 }
2251
2252
2253 /*
2254 * 'cancel_job()' - Cancel a print job.
2255 */
2256
2257 static void
2258 cancel_job(http_t *http, /* I - HTTP connection */
2259 const char *uri, /* I - printer-uri */
2260 int id, /* I - job-id */
2261 const char *resource, /* I - Resource path */
2262 const char *user, /* I - requesting-user-name */
2263 int version) /* I - IPP version */
2264 {
2265 ipp_t *request; /* Cancel-Job request */
2266
2267
2268 _cupsLangPrintFilter(stderr, "INFO", _("Canceling print job."));
2269
2270 request = ippNewRequest(IPP_CANCEL_JOB);
2271 ippSetVersion(request, version / 10, version % 10);
2272
2273 ippAddString(request, IPP_TAG_OPERATION, IPP_TAG_URI, "printer-uri",
2274 NULL, uri);
2275 ippAddInteger(request, IPP_TAG_OPERATION, IPP_TAG_INTEGER, "job-id", id);
2276
2277 if (user && user[0])
2278 ippAddString(request, IPP_TAG_OPERATION, IPP_TAG_NAME,
2279 "requesting-user-name", NULL, user);
2280
2281 /*
2282 * Do the request...
2283 */
2284
2285 ippDelete(cupsDoRequest(http, request, resource));
2286 }
2287
2288
2289 /*
2290 * 'check_printer_state()' - Check the printer state.
2291 */
2292
2293 static ipp_pstate_t /* O - Current printer-state */
2294 check_printer_state(
2295 http_t *http, /* I - HTTP connection */
2296 const char *uri, /* I - Printer URI */
2297 const char *resource, /* I - Resource path */
2298 const char *user, /* I - Username, if any */
2299 int version) /* I - IPP version */
2300 {
2301 ipp_t *request, /* IPP request */
2302 *response; /* IPP response */
2303 ipp_attribute_t *attr; /* Attribute in response */
2304 ipp_pstate_t printer_state = IPP_PRINTER_STOPPED;
2305 /* Current printer-state */
2306
2307
2308 /*
2309 * Send a Get-Printer-Attributes request and log the results...
2310 */
2311
2312 request = ippNewRequest(IPP_GET_PRINTER_ATTRIBUTES);
2313 ippSetVersion(request, version / 10, version % 10);
2314
2315 ippAddString(request, IPP_TAG_OPERATION, IPP_TAG_URI, "printer-uri",
2316 NULL, uri);
2317
2318 if (user && user[0])
2319 ippAddString(request, IPP_TAG_OPERATION, IPP_TAG_NAME,
2320 "requesting-user-name", NULL, user);
2321
2322 ippAddStrings(request, IPP_TAG_OPERATION, IPP_TAG_KEYWORD,
2323 "requested-attributes",
2324 (int)(sizeof(pattrs) / sizeof(pattrs[0])), NULL, pattrs);
2325
2326 if ((response = cupsDoRequest(http, request, resource)) != NULL)
2327 {
2328 report_printer_state(response);
2329
2330 if ((attr = ippFindAttribute(response, "printer-state",
2331 IPP_TAG_ENUM)) != NULL)
2332 printer_state = (ipp_pstate_t)attr->values[0].integer;
2333
2334 ippDelete(response);
2335 }
2336
2337 fprintf(stderr, "DEBUG: Get-Printer-Attributes: %s (%s)\n",
2338 ippErrorString(cupsLastError()), cupsLastErrorString());
2339
2340 /*
2341 * Return the printer-state value...
2342 */
2343
2344 return (printer_state);
2345 }
2346
2347
2348 /*
2349 * 'monitor_printer()' - Monitor the printer state.
2350 */
2351
2352 static void * /* O - Thread exit code */
2353 monitor_printer(
2354 _cups_monitor_t *monitor) /* I - Monitoring data */
2355 {
2356 http_t *http; /* Connection to printer */
2357 ipp_t *request, /* IPP request */
2358 *response; /* IPP response */
2359 ipp_attribute_t *attr; /* Attribute in response */
2360 int delay, /* Current delay */
2361 prev_delay; /* Previous delay */
2362 ipp_op_t job_op; /* Operation to use */
2363 int job_id; /* Job ID */
2364 const char *job_name; /* Job name */
2365 ipp_jstate_t job_state; /* Job state */
2366 const char *job_user; /* Job originating user name */
2367 int password_tries = 0; /* Password tries */
2368
2369
2370 /*
2371 * Make a copy of the printer connection...
2372 */
2373
2374 http = httpConnect2(monitor->hostname, monitor->port, NULL, AF_UNSPEC,
2375 monitor->encryption, 1, 0, NULL);
2376 httpSetTimeout(http, 30.0, timeout_cb, NULL);
2377 if (username[0])
2378 cupsSetUser(username);
2379
2380 cupsSetPasswordCB2((cups_password_cb2_t)password_cb, &password_tries);
2381
2382 /*
2383 * Loop until the job is canceled, aborted, or completed.
2384 */
2385
2386 delay = _cupsNextDelay(0, &prev_delay);
2387
2388 monitor->job_reasons = 0;
2389
2390 while (monitor->job_state < IPP_JOB_CANCELED && !job_canceled)
2391 {
2392 /*
2393 * Reconnect to the printer...
2394 */
2395
2396 if (!httpReconnect(http))
2397 {
2398 /*
2399 * Connected, so check on the printer state...
2400 */
2401
2402 monitor->printer_state = check_printer_state(http, monitor->uri,
2403 monitor->resource,
2404 monitor->user,
2405 monitor->version);
2406 if (cupsLastError() <= IPP_OK_CONFLICT)
2407 password_tries = 0;
2408
2409 if (monitor->job_id == 0 && monitor->create_job)
2410 {
2411 /*
2412 * No job-id yet, so continue...
2413 */
2414
2415 goto monitor_disconnect;
2416 }
2417
2418 /*
2419 * Check the status of the job itself...
2420 */
2421
2422 job_op = (monitor->job_id > 0 && monitor->get_job_attrs) ?
2423 IPP_GET_JOB_ATTRIBUTES : IPP_GET_JOBS;
2424 request = ippNewRequest(job_op);
2425 ippSetVersion(request, monitor->version / 10, monitor->version % 10);
2426
2427 ippAddString(request, IPP_TAG_OPERATION, IPP_TAG_URI, "printer-uri",
2428 NULL, monitor->uri);
2429 if (job_op == IPP_GET_JOB_ATTRIBUTES)
2430 ippAddInteger(request, IPP_TAG_OPERATION, IPP_TAG_INTEGER, "job-id",
2431 monitor->job_id);
2432
2433 if (monitor->user && monitor->user[0])
2434 ippAddString(request, IPP_TAG_OPERATION, IPP_TAG_NAME,
2435 "requesting-user-name", NULL, monitor->user);
2436
2437 ippAddStrings(request, IPP_TAG_OPERATION, IPP_TAG_KEYWORD,
2438 "requested-attributes",
2439 (int)(sizeof(jattrs) / sizeof(jattrs[0])), NULL, jattrs);
2440
2441 /*
2442 * Do the request...
2443 */
2444
2445 response = cupsDoRequest(http, request, monitor->resource);
2446
2447 fprintf(stderr, "DEBUG: (monitor) %s: %s (%s)\n", ippOpString(job_op),
2448 ippErrorString(cupsLastError()), cupsLastErrorString());
2449
2450 if (cupsLastError() <= IPP_OK_CONFLICT)
2451 password_tries = 0;
2452
2453 if (job_op == IPP_GET_JOB_ATTRIBUTES)
2454 {
2455 if ((attr = ippFindAttribute(response, "job-state",
2456 IPP_TAG_ENUM)) != NULL)
2457 monitor->job_state = (ipp_jstate_t)attr->values[0].integer;
2458 else
2459 monitor->job_state = IPP_JOB_COMPLETED;
2460 }
2461 else if (response)
2462 {
2463 for (attr = response->attrs; attr; attr = attr->next)
2464 {
2465 job_id = 0;
2466 job_name = NULL;
2467 job_state = IPP_JOB_PENDING;
2468 job_user = NULL;
2469
2470 while (attr && attr->group_tag != IPP_TAG_JOB)
2471 attr = attr->next;
2472
2473 if (!attr)
2474 break;
2475
2476 while (attr && attr->group_tag == IPP_TAG_JOB)
2477 {
2478 if (!strcmp(attr->name, "job-id") &&
2479 attr->value_tag == IPP_TAG_INTEGER)
2480 job_id = attr->values[0].integer;
2481 else if (!strcmp(attr->name, "job-name") &&
2482 (attr->value_tag == IPP_TAG_NAME ||
2483 attr->value_tag == IPP_TAG_NAMELANG))
2484 job_name = attr->values[0].string.text;
2485 else if (!strcmp(attr->name, "job-state") &&
2486 attr->value_tag == IPP_TAG_ENUM)
2487 job_state = (ipp_jstate_t)attr->values[0].integer;
2488 else if (!strcmp(attr->name, "job-originating-user-name") &&
2489 (attr->value_tag == IPP_TAG_NAME ||
2490 attr->value_tag == IPP_TAG_NAMELANG))
2491 job_user = attr->values[0].string.text;
2492
2493 attr = attr->next;
2494 }
2495
2496 if (job_id > 0 && job_name && !strcmp(job_name, monitor->job_name) &&
2497 job_user && monitor->user && !strcmp(job_user, monitor->user))
2498 {
2499 monitor->job_id = job_id;
2500 monitor->job_state = job_state;
2501 break;
2502 }
2503
2504 if (!attr)
2505 break;
2506 }
2507 }
2508
2509 fprintf(stderr, "DEBUG: (monitor) job-state = %s\n",
2510 ippEnumString("job-state", monitor->job_state));
2511
2512 if (!job_canceled &&
2513 (monitor->job_state == IPP_JOB_CANCELED ||
2514 monitor->job_state == IPP_JOB_ABORTED))
2515 {
2516 job_canceled = -1;
2517 fprintf(stderr, "DEBUG: (monitor) job_canceled = -1\n");
2518 }
2519
2520 if ((attr = ippFindAttribute(response, "job-state-reasons",
2521 IPP_TAG_KEYWORD)) != NULL)
2522 {
2523 int i, new_reasons = 0; /* Looping var, new reasons */
2524
2525 for (i = 0; i < attr->num_values; i ++)
2526 {
2527 if (!strcmp(attr->values[i].string.text,
2528 "account-authorization-failed"))
2529 new_reasons |= _CUPS_JSR_ACCOUNT_AUTHORIZATION_FAILED;
2530 else if (!strcmp(attr->values[i].string.text, "account-closed"))
2531 new_reasons |= _CUPS_JSR_ACCOUNT_CLOSED;
2532 else if (!strcmp(attr->values[i].string.text, "account-info-needed"))
2533 new_reasons |= _CUPS_JSR_ACCOUNT_INFO_NEEDED;
2534 else if (!strcmp(attr->values[i].string.text,
2535 "account-limit-reached"))
2536 new_reasons |= _CUPS_JSR_ACCOUNT_LIMIT_REACHED;
2537 else if (!strcmp(attr->values[i].string.text, "job-password-wait"))
2538 new_reasons |= _CUPS_JSR_JOB_PASSWORD_WAIT;
2539 else if (!strcmp(attr->values[i].string.text, "job-release-wait"))
2540 new_reasons |= _CUPS_JSR_JOB_RELEASE_WAIT;
2541 if (!job_canceled &&
2542 (!strncmp(attr->values[i].string.text, "job-canceled-", 13) || !strcmp(attr->values[i].string.text, "aborted-by-system")))
2543 job_canceled = 1;
2544 }
2545
2546 if (new_reasons != monitor->job_reasons)
2547 {
2548 if (new_reasons & _CUPS_JSR_ACCOUNT_AUTHORIZATION_FAILED)
2549 fputs("JOBSTATE: account-authorization-failed\n", stderr);
2550 else if (new_reasons & _CUPS_JSR_ACCOUNT_CLOSED)
2551 fputs("JOBSTATE: account-closed\n", stderr);
2552 else if (new_reasons & _CUPS_JSR_ACCOUNT_INFO_NEEDED)
2553 fputs("JOBSTATE: account-info-needed\n", stderr);
2554 else if (new_reasons & _CUPS_JSR_ACCOUNT_LIMIT_REACHED)
2555 fputs("JOBSTATE: account-limit-reached\n", stderr);
2556 else if (new_reasons & _CUPS_JSR_JOB_PASSWORD_WAIT)
2557 fputs("JOBSTATE: job-password-wait\n", stderr);
2558 else if (new_reasons & _CUPS_JSR_JOB_RELEASE_WAIT)
2559 fputs("JOBSTATE: job-release-wait\n", stderr);
2560 else
2561 fputs("JOBSTATE: job-printing\n", stderr);
2562
2563 monitor->job_reasons = new_reasons;
2564 }
2565 }
2566
2567 ippDelete(response);
2568
2569 fprintf(stderr, "DEBUG: (monitor) job-state = %s\n",
2570 ippEnumString("job-state", monitor->job_state));
2571
2572 if (!job_canceled &&
2573 (monitor->job_state == IPP_JOB_CANCELED ||
2574 monitor->job_state == IPP_JOB_ABORTED))
2575 job_canceled = -1;
2576
2577 /*
2578 * Disconnect from the printer - we'll reconnect on the next poll...
2579 */
2580
2581 monitor_disconnect:
2582
2583 _httpDisconnect(http);
2584 }
2585
2586 /*
2587 * Sleep for N seconds...
2588 */
2589
2590 sleep((unsigned)delay);
2591
2592 delay = _cupsNextDelay(delay, &prev_delay);
2593 }
2594
2595 /*
2596 * Cancel the job if necessary...
2597 */
2598
2599 if (job_canceled > 0 && monitor->job_id > 0)
2600 {
2601 if (!httpReconnect(http))
2602 {
2603 cancel_job(http, monitor->uri, monitor->job_id, monitor->resource,
2604 monitor->user, monitor->version);
2605
2606 if (cupsLastError() > IPP_OK_CONFLICT)
2607 {
2608 fprintf(stderr, "DEBUG: (monitor) cancel_job() = %s\n", cupsLastErrorString());
2609 _cupsLangPrintFilter(stderr, "ERROR", _("Unable to cancel print job."));
2610 }
2611 }
2612 }
2613
2614 /*
2615 * Cleanup and return...
2616 */
2617
2618 httpClose(http);
2619
2620 return (NULL);
2621 }
2622
2623
2624 /*
2625 * 'new_request()' - Create a new print creation or validation request.
2626 */
2627
2628 static ipp_t * /* O - Request data */
2629 new_request(
2630 ipp_op_t op, /* I - IPP operation code */
2631 int version, /* I - IPP version number */
2632 const char *uri, /* I - printer-uri value */
2633 const char *user, /* I - requesting-user-name value */
2634 const char *title, /* I - job-name value */
2635 int num_options, /* I - Number of options to send */
2636 cups_option_t *options, /* I - Options to send */
2637 const char *compression, /* I - compression value or NULL */
2638 int copies, /* I - copies value or 0 */
2639 const char *format, /* I - document-format value or NULL */
2640 _ppd_cache_t *pc, /* I - PPD cache and mapping data */
2641 ppd_file_t *ppd, /* I - PPD file data */
2642 ipp_attribute_t *media_col_sup, /* I - media-col-supported values */
2643 ipp_attribute_t *doc_handling_sup, /* I - multiple-document-handling-supported values */
2644 ipp_attribute_t *print_color_mode_sup)
2645 /* I - Printer supports print-color-mode */
2646 {
2647 ipp_t *request; /* Request data */
2648 const char *keyword; /* PWG keyword */
2649 ipp_tag_t group; /* Current group */
2650 ipp_attribute_t *attr; /* Current attribute */
2651 char buffer[1024]; /* Value buffer */
2652
2653
2654 /*
2655 * Create the IPP request...
2656 */
2657
2658 request = ippNewRequest(op);
2659 ippSetVersion(request, version / 10, version % 10);
2660
2661 fprintf(stderr, "DEBUG: %s IPP/%d.%d\n",
2662 ippOpString(request->request.op.operation_id),
2663 request->request.op.version[0],
2664 request->request.op.version[1]);
2665
2666 /*
2667 * Add standard attributes...
2668 */
2669
2670 ippAddString(request, IPP_TAG_OPERATION, IPP_TAG_URI, "printer-uri", NULL, uri);
2671 fprintf(stderr, "DEBUG: printer-uri=\"%s\"\n", uri);
2672
2673 if (user && *user)
2674 {
2675 ippAddString(request, IPP_TAG_OPERATION, IPP_TAG_NAME, "requesting-user-name", NULL, user);
2676 fprintf(stderr, "DEBUG: requesting-user-name=\"%s\"\n", user);
2677 }
2678
2679 if (title && *title)
2680 {
2681 ippAddString(request, IPP_TAG_OPERATION, IPP_TAG_NAME, "job-name", NULL, title);
2682 fprintf(stderr, "DEBUG: job-name=\"%s\"\n", title);
2683 }
2684
2685 if (format && op != IPP_CREATE_JOB)
2686 {
2687 ippAddString(request, IPP_TAG_OPERATION, IPP_TAG_MIMETYPE, "document-format", NULL, format);
2688 fprintf(stderr, "DEBUG: document-format=\"%s\"\n", format);
2689 }
2690
2691 #ifdef HAVE_LIBZ
2692 if (compression && op != IPP_OP_CREATE_JOB && op != IPP_OP_VALIDATE_JOB)
2693 {
2694 ippAddString(request, IPP_TAG_OPERATION, IPP_TAG_KEYWORD, "compression", NULL, compression);
2695 fprintf(stderr, "DEBUG: compression=\"%s\"\n", compression);
2696 }
2697 #endif /* HAVE_LIBZ */
2698
2699 /*
2700 * Handle options on the command-line...
2701 */
2702
2703 if (num_options > 0)
2704 {
2705 if (pc)
2706 {
2707 /*
2708 * Send standard IPP attributes...
2709 */
2710
2711 fputs("DEBUG: Adding standard IPP operation/job attributes.\n", stderr);
2712
2713 copies = _cupsConvertOptions(request, ppd, pc, media_col_sup, doc_handling_sup, print_color_mode_sup, user, format, copies, num_options, options);
2714
2715 /*
2716 * Map FaxOut options...
2717 */
2718
2719 if ((keyword = cupsGetOption("phone", num_options, options)) != NULL)
2720 {
2721 ipp_t *destination; /* destination collection */
2722 char phone[1024], /* Phone number string */
2723 *ptr, /* Pointer into string */
2724 tel_uri[1024]; /* tel: URI */
2725 static const char * const allowed = "0123456789#*-+.()";
2726 /* Allowed characters */
2727
2728 destination = ippNew();
2729
2730 /*
2731 * Unescape and filter out spaces and other characters that are not
2732 * allowed in a tel: URI.
2733 */
2734
2735 _httpDecodeURI(phone, keyword, sizeof(phone));
2736 for (ptr = phone; *ptr;)
2737 {
2738 if (!strchr(allowed, *ptr))
2739 _cups_strcpy(ptr, ptr + 1);
2740 else
2741 ptr ++;
2742 }
2743
2744 httpAssembleURI(HTTP_URI_CODING_ALL, tel_uri, sizeof(tel_uri), "tel", NULL, NULL, 0, phone);
2745 ippAddString(destination, IPP_TAG_JOB, IPP_TAG_URI, "destination-uri", NULL, tel_uri);
2746
2747 if ((keyword = cupsGetOption("faxPrefix", num_options,
2748 options)) != NULL && *keyword)
2749 {
2750 char predial[1024]; /* Pre-dial string */
2751
2752 _httpDecodeURI(predial, keyword, sizeof(predial));
2753 ippAddString(destination, IPP_TAG_JOB, IPP_TAG_TEXT, "pre-dial-string", NULL, predial);
2754 }
2755
2756 ippAddCollection(request, IPP_TAG_JOB, "destination-uris", destination);
2757 ippDelete(destination);
2758 }
2759 }
2760 else
2761 {
2762 /*
2763 * When talking to another CUPS server, send all options...
2764 */
2765
2766 fputs("DEBUG: Adding all operation/job attributes.\n", stderr);
2767 cupsEncodeOptions2(request, num_options, options, IPP_TAG_OPERATION);
2768 cupsEncodeOptions2(request, num_options, options, IPP_TAG_JOB);
2769 }
2770
2771 if (copies > 1 && (!pc || copies <= pc->max_copies))
2772 ippAddInteger(request, IPP_TAG_JOB, IPP_TAG_INTEGER, "copies", copies);
2773 }
2774
2775 fprintf(stderr, "DEBUG: IPP/%d.%d %s #%d\n", version / 10, version % 10,
2776 ippOpString(ippGetOperation(request)), ippGetRequestId(request));
2777 for (group = IPP_TAG_ZERO, attr = ippFirstAttribute(request);
2778 attr;
2779 attr = ippNextAttribute(request))
2780 {
2781 const char *name = ippGetName(attr);
2782
2783 if (!name)
2784 {
2785 group = IPP_TAG_ZERO;
2786 continue;
2787 }
2788
2789 if (group != ippGetGroupTag(attr))
2790 {
2791 group = ippGetGroupTag(attr);
2792 fprintf(stderr, "DEBUG: ---- %s ----\n", ippTagString(group));
2793 }
2794
2795 ippAttributeString(attr, buffer, sizeof(buffer));
2796 fprintf(stderr, "DEBUG: %s %s%s %s\n", name,
2797 ippGetCount(attr) > 1 ? "1setOf " : "",
2798 ippTagString(ippGetValueTag(attr)), buffer);
2799 }
2800
2801 fprintf(stderr, "DEBUG: ---- %s ----\n", ippTagString(IPP_TAG_END));
2802
2803 return (request);
2804 }
2805
2806
2807 /*
2808 * 'password_cb()' - Disable the password prompt for cupsDoFileRequest().
2809 */
2810
2811 static const char * /* O - Password */
2812 password_cb(const char *prompt, /* I - Prompt (not used) */
2813 http_t *http, /* I - Connection */
2814 const char *method, /* I - Request method (not used) */
2815 const char *resource, /* I - Resource path (not used) */
2816 int *password_tries) /* I - Password tries */
2817 {
2818 char def_username[HTTP_MAX_VALUE]; /* Default username */
2819
2820
2821 fprintf(stderr, "DEBUG: password_cb(prompt=\"%s\", http=%p, method=\"%s\", "
2822 "resource=\"%s\", password_tries=%p(%d)), password=%p\n",
2823 prompt, http, method, resource, password_tries, *password_tries,
2824 password);
2825
2826 (void)prompt;
2827 (void)method;
2828 (void)resource;
2829
2830 if (!uri_credentials)
2831 {
2832 /*
2833 * Remember that we need to authenticate...
2834 */
2835
2836 auth_info_required = "username,password";
2837
2838 if (httpGetSubField(http, HTTP_FIELD_WWW_AUTHENTICATE, "username",
2839 def_username))
2840 {
2841 char quoted[HTTP_MAX_VALUE * 2 + 4];
2842 /* Quoted string */
2843
2844 fprintf(stderr, "ATTR: auth-info-default=%s,\n",
2845 quote_string(def_username, quoted, sizeof(quoted)));
2846 }
2847 }
2848
2849 if (password && *password && *password_tries < 3)
2850 {
2851 (*password_tries) ++;
2852
2853 cupsSetUser(username);
2854
2855 return (password);
2856 }
2857 else
2858 {
2859 /*
2860 * Give up after 3 tries or if we don't have a password to begin with...
2861 */
2862
2863 return (NULL);
2864 }
2865 }
2866
2867
2868 /*
2869 * 'quote_string()' - Quote a string value.
2870 */
2871
2872 static const char * /* O - Quoted string */
2873 quote_string(const char *s, /* I - String */
2874 char *q, /* I - Quoted string buffer */
2875 size_t qsize) /* I - Size of quoted string buffer */
2876 {
2877 char *qptr, /* Pointer into string buffer */
2878 *qend; /* End of string buffer */
2879
2880
2881 qptr = q;
2882 qend = q + qsize - 5;
2883
2884 if (qend < q)
2885 {
2886 *q = '\0';
2887 return (q);
2888 }
2889
2890 *qptr++ = '\'';
2891 *qptr++ = '\"';
2892
2893 while (*s && qptr < qend)
2894 {
2895 if (*s == '\\' || *s == '\"' || *s == '\'')
2896 {
2897 if (qptr < (qend - 4))
2898 {
2899 *qptr++ = '\\';
2900 *qptr++ = '\\';
2901 *qptr++ = '\\';
2902 }
2903 else
2904 break;
2905 }
2906
2907 *qptr++ = *s++;
2908 }
2909
2910 *qptr++ = '\"';
2911 *qptr++ = '\'';
2912 *qptr = '\0';
2913
2914 return (q);
2915 }
2916
2917
2918 /*
2919 * 'report_attr()' - Report an IPP attribute value.
2920 */
2921
2922 static void
2923 report_attr(ipp_attribute_t *attr) /* I - Attribute */
2924 {
2925 int i; /* Looping var */
2926 char value[1024], /* Value string */
2927 *valptr; /* Pointer into value string */
2928 const char *cached; /* Cached attribute */
2929
2930
2931 /*
2932 * Convert the attribute values into quoted strings...
2933 */
2934
2935 for (i = 0, valptr = value;
2936 i < attr->num_values && valptr < (value + sizeof(value) - 10);
2937 i ++)
2938 {
2939 if (i > 0)
2940 *valptr++ = ',';
2941
2942 switch (attr->value_tag)
2943 {
2944 case IPP_TAG_INTEGER :
2945 case IPP_TAG_ENUM :
2946 snprintf(valptr, sizeof(value) - (size_t)(valptr - value), "%d", attr->values[i].integer);
2947 valptr += strlen(valptr);
2948 break;
2949
2950 case IPP_TAG_TEXT :
2951 case IPP_TAG_NAME :
2952 case IPP_TAG_KEYWORD :
2953 quote_string(attr->values[i].string.text, valptr, (size_t)(value + sizeof(value) - valptr));
2954 valptr += strlen(valptr);
2955 break;
2956
2957 default :
2958 /*
2959 * Unsupported value type...
2960 */
2961
2962 return;
2963 }
2964 }
2965
2966 *valptr = '\0';
2967
2968 _cupsMutexLock(&report_mutex);
2969
2970 if ((cached = cupsGetOption(attr->name, num_attr_cache,
2971 attr_cache)) == NULL || strcmp(cached, value))
2972 {
2973 /*
2974 * Tell the scheduler about the new values...
2975 */
2976
2977 num_attr_cache = cupsAddOption(attr->name, value, num_attr_cache,
2978 &attr_cache);
2979 fprintf(stderr, "ATTR: %s=%s\n", attr->name, value);
2980 }
2981
2982 _cupsMutexUnlock(&report_mutex);
2983 }
2984
2985
2986 /*
2987 * 'report_printer_state()' - Report the printer state.
2988 */
2989
2990 static void
2991 report_printer_state(ipp_t *ipp) /* I - IPP response */
2992 {
2993 ipp_attribute_t *pa, /* printer-alert */
2994 *pam, /* printer-alert-message */
2995 *pmja, /* printer-mandatory-job-attributes */
2996 *psm, /* printer-state-message */
2997 *reasons, /* printer-state-reasons */
2998 *marker; /* marker-* attributes */
2999 char value[1024], /* State/message string */
3000 *valptr; /* Pointer into string */
3001 static int ipp_supplies = -1;
3002 /* Report supply levels? */
3003
3004
3005 /*
3006 * Report alerts and messages...
3007 */
3008
3009 if ((pa = ippFindAttribute(ipp, "printer-alert", IPP_TAG_TEXT)) != NULL)
3010 report_attr(pa);
3011
3012 if ((pam = ippFindAttribute(ipp, "printer-alert-message",
3013 IPP_TAG_TEXT)) != NULL)
3014 report_attr(pam);
3015
3016 if ((pmja = ippFindAttribute(ipp, "printer-mandatory-job-attributes", IPP_TAG_KEYWORD)) != NULL)
3017 {
3018 int i, /* Looping var */
3019 count = ippGetCount(pmja); /* Number of values */
3020
3021 for (i = 0, valptr = value; i < count; i ++, valptr += strlen(valptr))
3022 {
3023 if (i)
3024 snprintf(valptr, sizeof(value) - (size_t)(valptr - value), " %s", ippGetString(pmja, i, NULL));
3025 else
3026 strlcpy(value, ippGetString(pmja, i, NULL), sizeof(value));
3027 }
3028
3029 if (strcmp(value, mandatory_attrs))
3030 {
3031 strlcpy(mandatory_attrs, value, sizeof(mandatory_attrs));
3032 fprintf(stderr, "PPD: cupsMandatory=\"%s\"\n", value);
3033 }
3034 }
3035
3036 if ((psm = ippFindAttribute(ipp, "printer-state-message",
3037 IPP_TAG_TEXT)) != NULL)
3038 {
3039 char *ptr; /* Pointer into message */
3040
3041
3042 strlcpy(value, "INFO: ", sizeof(value));
3043 for (ptr = psm->values[0].string.text, valptr = value + 6;
3044 *ptr && valptr < (value + sizeof(value) - 6);
3045 ptr ++)
3046 {
3047 if (*ptr < ' ' && *ptr > 0 && *ptr != '\t')
3048 {
3049 /*
3050 * Substitute "<XX>" for the control character; sprintf is safe because
3051 * we always leave 6 chars free at the end...
3052 */
3053
3054 sprintf(valptr, "<%02X>", *ptr);
3055 valptr += 4;
3056 }
3057 else
3058 *valptr++ = *ptr;
3059 }
3060
3061 *valptr++ = '\n';
3062 *valptr = '\0';
3063
3064 fputs(value, stderr);
3065 }
3066
3067 /*
3068 * Now report printer-state-reasons, filtering out some of the reasons we never
3069 * want to set...
3070 */
3071
3072 if ((reasons = ippFindAttribute(ipp, "printer-state-reasons",
3073 IPP_TAG_KEYWORD)) == NULL)
3074 return;
3075
3076 update_reasons(reasons, NULL);
3077
3078 /*
3079 * Relay the current marker-* attribute values...
3080 */
3081
3082 if (ipp_supplies < 0)
3083 {
3084 ppd_file_t *ppd; /* PPD file */
3085 ppd_attr_t *ppdattr; /* Attribute in PPD file */
3086
3087 if ((ppd = ppdOpenFile(getenv("PPD"))) != NULL &&
3088 (ppdattr = ppdFindAttr(ppd, "cupsIPPSupplies", NULL)) != NULL &&
3089 ppdattr->value && _cups_strcasecmp(ppdattr->value, "true"))
3090 ipp_supplies = 0;
3091 else
3092 ipp_supplies = 1;
3093
3094 ppdClose(ppd);
3095 }
3096
3097 if (ipp_supplies > 0)
3098 {
3099 if ((marker = ippFindAttribute(ipp, "marker-colors", IPP_TAG_NAME)) != NULL)
3100 report_attr(marker);
3101 if ((marker = ippFindAttribute(ipp, "marker-high-levels",
3102 IPP_TAG_INTEGER)) != NULL)
3103 report_attr(marker);
3104 if ((marker = ippFindAttribute(ipp, "marker-levels",
3105 IPP_TAG_INTEGER)) != NULL)
3106 report_attr(marker);
3107 if ((marker = ippFindAttribute(ipp, "marker-low-levels",
3108 IPP_TAG_INTEGER)) != NULL)
3109 report_attr(marker);
3110 if ((marker = ippFindAttribute(ipp, "marker-message",
3111 IPP_TAG_TEXT)) != NULL)
3112 report_attr(marker);
3113 if ((marker = ippFindAttribute(ipp, "marker-names", IPP_TAG_NAME)) != NULL)
3114 report_attr(marker);
3115 if ((marker = ippFindAttribute(ipp, "marker-types",
3116 IPP_TAG_KEYWORD)) != NULL)
3117 report_attr(marker);
3118 }
3119 }
3120
3121
3122 #if defined(HAVE_GSSAPI) && defined(HAVE_XPC)
3123 /*
3124 * 'run_as_user()' - Run the IPP backend as the printing user.
3125 *
3126 * This function uses an XPC-based user agent to run the backend as the printing
3127 * user. We need to do this in order to have access to the user's Kerberos
3128 * credentials.
3129 */
3130
3131 static int /* O - Exit status */
3132 run_as_user(char *argv[], /* I - Command-line arguments */
3133 uid_t uid, /* I - User ID */
3134 const char *device_uri, /* I - Device URI */
3135 int fd) /* I - File to print */
3136 {
3137 const char *auth_negotiate;/* AUTH_NEGOTIATE env var */
3138 xpc_connection_t conn; /* Connection to XPC service */
3139 xpc_object_t request; /* Request message dictionary */
3140 __block xpc_object_t response; /* Response message dictionary */
3141 dispatch_semaphore_t sem; /* Semaphore for waiting for response */
3142 int status = CUPS_BACKEND_FAILED;
3143 /* Status of request */
3144
3145
3146 fprintf(stderr, "DEBUG: Running IPP backend as UID %d.\n", (int)uid);
3147
3148 /*
3149 * Connect to the user agent for the specified UID...
3150 */
3151
3152 conn = xpc_connection_create_mach_service(kPMPrintUIToolAgent,
3153 dispatch_get_global_queue(0, 0), 0);
3154 if (!conn)
3155 {
3156 _cupsLangPrintFilter(stderr, "ERROR",
3157 _("Unable to start backend process."));
3158 fputs("DEBUG: Unable to create connection to agent.\n", stderr);
3159 goto cleanup;
3160 }
3161
3162 xpc_connection_set_event_handler(conn,
3163 ^(xpc_object_t event)
3164 {
3165 xpc_type_t messageType = xpc_get_type(event);
3166
3167 if (messageType == XPC_TYPE_ERROR)
3168 {
3169 if (event == XPC_ERROR_CONNECTION_INTERRUPTED)
3170 fprintf(stderr, "DEBUG: Interrupted connection to service %s.\n",
3171 xpc_connection_get_name(conn));
3172 else if (event == XPC_ERROR_CONNECTION_INVALID)
3173 fprintf(stderr, "DEBUG: Connection invalid for service %s.\n",
3174 xpc_connection_get_name(conn));
3175 else
3176 fprintf(stderr, "DEBUG: Unxpected error for service %s: %s\n",
3177 xpc_connection_get_name(conn),
3178 xpc_dictionary_get_string(event, XPC_ERROR_KEY_DESCRIPTION));
3179 }
3180 });
3181 xpc_connection_set_target_uid(conn, uid);
3182 xpc_connection_resume(conn);
3183
3184 /*
3185 * Try starting the backend...
3186 */
3187
3188 request = xpc_dictionary_create(NULL, NULL, 0);
3189 xpc_dictionary_set_int64(request, "command", kPMStartJob);
3190 xpc_dictionary_set_string(request, "device-uri", device_uri);
3191 xpc_dictionary_set_string(request, "job-id", argv[1]);
3192 xpc_dictionary_set_string(request, "user", argv[2]);
3193 xpc_dictionary_set_string(request, "title", argv[3]);
3194 xpc_dictionary_set_string(request, "copies", argv[4]);
3195 xpc_dictionary_set_string(request, "options", argv[5]);
3196 xpc_dictionary_set_string(request, "auth-info-required",
3197 getenv("AUTH_INFO_REQUIRED"));
3198 if ((auth_negotiate = getenv("AUTH_NEGOTIATE")) != NULL)
3199 xpc_dictionary_set_string(request, "auth-negotiate", auth_negotiate);
3200 xpc_dictionary_set_fd(request, "stdin", fd);
3201 xpc_dictionary_set_fd(request, "stderr", 2);
3202 xpc_dictionary_set_fd(request, "side-channel", CUPS_SC_FD);
3203
3204 sem = dispatch_semaphore_create(0);
3205 response = NULL;
3206
3207 xpc_connection_send_message_with_reply(conn, request,
3208 dispatch_get_global_queue(0,0),
3209 ^(xpc_object_t reply)
3210 {
3211 /* Save the response and wake up */
3212 if (xpc_get_type(reply)
3213 == XPC_TYPE_DICTIONARY)
3214 response = xpc_retain(reply);
3215
3216 dispatch_semaphore_signal(sem);
3217 });
3218
3219 dispatch_semaphore_wait(sem, DISPATCH_TIME_FOREVER);
3220 xpc_release(request);
3221 dispatch_release(sem);
3222
3223 if (response)
3224 {
3225 child_pid = (pid_t)xpc_dictionary_get_int64(response, "child-pid");
3226
3227 xpc_release(response);
3228
3229 if (child_pid)
3230 fprintf(stderr, "DEBUG: Child PID=%d.\n", (int)child_pid);
3231 else
3232 {
3233 _cupsLangPrintFilter(stderr, "ERROR",
3234 _("Unable to start backend process."));
3235 fputs("DEBUG: No child PID.\n", stderr);
3236 goto cleanup;
3237 }
3238 }
3239 else
3240 {
3241 _cupsLangPrintFilter(stderr, "ERROR",
3242 _("Unable to start backend process."));
3243 fputs("DEBUG: No reply from agent.\n", stderr);
3244 goto cleanup;
3245 }
3246
3247 /*
3248 * Then wait for the backend to finish...
3249 */
3250
3251 request = xpc_dictionary_create(NULL, NULL, 0);
3252 xpc_dictionary_set_int64(request, "command", kPMWaitForJob);
3253 xpc_dictionary_set_fd(request, "stderr", 2);
3254
3255 sem = dispatch_semaphore_create(0);
3256 response = NULL;
3257
3258 xpc_connection_send_message_with_reply(conn, request,
3259 dispatch_get_global_queue(0,0),
3260 ^(xpc_object_t reply)
3261 {
3262 /* Save the response and wake up */
3263 if (xpc_get_type(reply)
3264 == XPC_TYPE_DICTIONARY)
3265 response = xpc_retain(reply);
3266
3267 dispatch_semaphore_signal(sem);
3268 });
3269
3270 dispatch_semaphore_wait(sem, DISPATCH_TIME_FOREVER);
3271 xpc_release(request);
3272 dispatch_release(sem);
3273
3274 if (response)
3275 {
3276 status = (int)xpc_dictionary_get_int64(response, "status");
3277
3278 if (status == SIGTERM || status == SIGKILL || status == SIGPIPE)
3279 {
3280 fprintf(stderr, "DEBUG: Child terminated on signal %d.\n", status);
3281 status = CUPS_BACKEND_FAILED;
3282 }
3283 else if (WIFSIGNALED(status))
3284 {
3285 fprintf(stderr, "DEBUG: Child crashed on signal %d.\n", status);
3286 status = CUPS_BACKEND_STOP;
3287 }
3288 else if (WIFEXITED(status))
3289 {
3290 status = WEXITSTATUS(status);
3291 fprintf(stderr, "DEBUG: Child exited with status %d.\n", status);
3292 }
3293
3294 xpc_release(response);
3295 }
3296 else
3297 _cupsLangPrintFilter(stderr, "ERROR",
3298 _("Unable to get backend exit status."));
3299
3300 cleanup:
3301
3302 if (conn)
3303 {
3304 xpc_connection_cancel(conn);
3305 xpc_release(conn);
3306 }
3307
3308 return (status);
3309 }
3310 #endif /* HAVE_GSSAPI && HAVE_XPC */
3311
3312
3313 /*
3314 * 'sigterm_handler()' - Handle 'terminate' signals that stop the backend.
3315 */
3316
3317 static void
3318 sigterm_handler(int sig) /* I - Signal */
3319 {
3320 (void)sig; /* remove compiler warnings... */
3321
3322 write(2, "DEBUG: Got SIGTERM.\n", 20);
3323
3324 #if defined(HAVE_GSSAPI) && defined(HAVE_XPC)
3325 if (child_pid)
3326 {
3327 kill(child_pid, sig);
3328 child_pid = 0;
3329 }
3330 #endif /* HAVE_GSSAPI && HAVE_XPC */
3331
3332 if (!job_canceled)
3333 {
3334 /*
3335 * Flag that the job should be canceled...
3336 */
3337
3338 write(2, "DEBUG: sigterm_handler: job_canceled = 1.\n", 25);
3339
3340 job_canceled = 1;
3341 return;
3342 }
3343
3344 /*
3345 * The scheduler already tried to cancel us once, now just terminate
3346 * after removing our temp file!
3347 */
3348
3349 if (tmpfilename[0])
3350 unlink(tmpfilename);
3351
3352 _exit(1);
3353 }
3354
3355
3356 /*
3357 * 'timeout_cb()' - Handle HTTP timeouts.
3358 */
3359
3360 static int /* O - 1 to continue, 0 to cancel */
3361 timeout_cb(http_t *http, /* I - Connection to server (unused) */
3362 void *user_data) /* I - User data (unused) */
3363 {
3364 (void)http;
3365 (void)user_data;
3366
3367 return (!job_canceled);
3368 }
3369
3370
3371 /*
3372 * 'update_reasons()' - Update the printer-state-reasons values.
3373 */
3374
3375 static void
3376 update_reasons(ipp_attribute_t *attr, /* I - printer-state-reasons or NULL */
3377 const char *s) /* I - STATE: string or NULL */
3378 {
3379 char op; /* Add (+), remove (-), replace (\0) */
3380 cups_array_t *new_reasons; /* New reasons array */
3381 char *reason, /* Current reason */
3382 add[2048], /* Reasons added string */
3383 *addptr, /* Pointer into add string */
3384 rem[2048], /* Reasons removed string */
3385 *remptr; /* Pointer into remove string */
3386 const char *addprefix, /* Current add string prefix */
3387 *remprefix; /* Current remove string prefix */
3388
3389
3390 fprintf(stderr, "DEBUG: update_reasons(attr=%d(%s%s), s=\"%s\")\n",
3391 attr ? attr->num_values : 0, attr ? attr->values[0].string.text : "",
3392 attr && attr->num_values > 1 ? ",..." : "", s ? s : "(null)");
3393
3394 /*
3395 * Create an array of new reason keyword strings...
3396 */
3397
3398 if (attr)
3399 {
3400 int i; /* Looping var */
3401
3402 new_reasons = cupsArrayNew((cups_array_func_t)strcmp, NULL);
3403 op = '\0';
3404
3405 for (i = 0; i < attr->num_values; i ++)
3406 {
3407 reason = attr->values[i].string.text;
3408
3409 if (strcmp(reason, "none") &&
3410 strcmp(reason, "none-report") &&
3411 strcmp(reason, "paused") &&
3412 strncmp(reason, "spool-area-full", 15) &&
3413 strcmp(reason, "com.apple.print.recoverable-warning") &&
3414 strncmp(reason, "cups-", 5))
3415 cupsArrayAdd(new_reasons, reason);
3416 }
3417 }
3418 else if (s)
3419 {
3420 if (*s == '+' || *s == '-')
3421 op = *s++;
3422 else
3423 op = '\0';
3424
3425 new_reasons = _cupsArrayNewStrings(s, ',');
3426 }
3427 else
3428 return;
3429
3430 /*
3431 * Compute the changes...
3432 */
3433
3434 add[0] = '\0';
3435 addprefix = "STATE: +";
3436 addptr = add;
3437 rem[0] = '\0';
3438 remprefix = "STATE: -";
3439 remptr = rem;
3440
3441 fprintf(stderr, "DEBUG2: op='%c', new_reasons=%d, state_reasons=%d\n",
3442 op ? op : ' ', cupsArrayCount(new_reasons),
3443 cupsArrayCount(state_reasons));
3444
3445 _cupsMutexLock(&report_mutex);
3446
3447 if (op == '+')
3448 {
3449 /*
3450 * Add reasons...
3451 */
3452
3453 for (reason = (char *)cupsArrayFirst(new_reasons);
3454 reason;
3455 reason = (char *)cupsArrayNext(new_reasons))
3456 {
3457 if (!cupsArrayFind(state_reasons, reason))
3458 {
3459 if (!strncmp(reason, "cups-remote-", 12))
3460 {
3461 /*
3462 * If we are setting cups-remote-xxx, remove all other cups-remote-xxx
3463 * keywords...
3464 */
3465
3466 char *temp; /* Current reason in state_reasons */
3467
3468 cupsArraySave(state_reasons);
3469
3470 for (temp = (char *)cupsArrayFirst(state_reasons);
3471 temp;
3472 temp = (char *)cupsArrayNext(state_reasons))
3473 if (!strncmp(temp, "cups-remote-", 12))
3474 {
3475 snprintf(remptr, sizeof(rem) - (size_t)(remptr - rem), "%s%s", remprefix, temp);
3476 remptr += strlen(remptr);
3477 remprefix = ",";
3478
3479 cupsArrayRemove(state_reasons, temp);
3480 break;
3481 }
3482
3483 cupsArrayRestore(state_reasons);
3484 }
3485
3486 cupsArrayAdd(state_reasons, reason);
3487
3488 snprintf(addptr, sizeof(add) - (size_t)(addptr - add), "%s%s", addprefix, reason);
3489 addptr += strlen(addptr);
3490 addprefix = ",";
3491 }
3492 }
3493 }
3494 else if (op == '-')
3495 {
3496 /*
3497 * Remove reasons...
3498 */
3499
3500 for (reason = (char *)cupsArrayFirst(new_reasons);
3501 reason;
3502 reason = (char *)cupsArrayNext(new_reasons))
3503 {
3504 if (cupsArrayFind(state_reasons, reason))
3505 {
3506 snprintf(remptr, sizeof(rem) - (size_t)(remptr - rem), "%s%s", remprefix, reason);
3507 remptr += strlen(remptr);
3508 remprefix = ",";
3509
3510 cupsArrayRemove(state_reasons, reason);
3511 }
3512 }
3513 }
3514 else
3515 {
3516 /*
3517 * Replace reasons...
3518 */
3519
3520 for (reason = (char *)cupsArrayFirst(state_reasons);
3521 reason;
3522 reason = (char *)cupsArrayNext(state_reasons))
3523 {
3524 if (strncmp(reason, "cups-", 5) && !cupsArrayFind(new_reasons, reason))
3525 {
3526 snprintf(remptr, sizeof(rem) - (size_t)(remptr - rem), "%s%s", remprefix, reason);
3527 remptr += strlen(remptr);
3528 remprefix = ",";
3529
3530 cupsArrayRemove(state_reasons, reason);
3531 }
3532 }
3533
3534 for (reason = (char *)cupsArrayFirst(new_reasons);
3535 reason;
3536 reason = (char *)cupsArrayNext(new_reasons))
3537 {
3538 if (!cupsArrayFind(state_reasons, reason))
3539 {
3540 cupsArrayAdd(state_reasons, reason);
3541
3542 snprintf(addptr, sizeof(add) - (size_t)(addptr - add), "%s%s", addprefix, reason);
3543 addptr += strlen(addptr);
3544 addprefix = ",";
3545 }
3546 }
3547 }
3548
3549 _cupsMutexUnlock(&report_mutex);
3550
3551 /*
3552 * Report changes and return...
3553 */
3554
3555 if (add[0] && rem[0])
3556 fprintf(stderr, "%s\n%s\n", add, rem);
3557 else if (add[0])
3558 fprintf(stderr, "%s\n", add);
3559 else if (rem[0])
3560 fprintf(stderr, "%s\n", rem);
3561 }