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