Revert manifest to default one
[external/cups.git] / backend / ipp14.c
1 /*
2  * "$Id: ipp.c 8950 2010-01-14 22:40:19Z mike $"
3  *
4  *   IPP backend for the Common UNIX Printing System (CUPS).
5  *
6  *   Copyright 2007-2010 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  * Contents:
18  *
19  *   main()                 - Send a file to the printer or server.
20  *   cancel_job()           - Cancel a print job.
21  *   check_printer_state()  - Check the printer state...
22  *   compress_files()       - Compress print files...
23  *   password_cb()          - Disable the password prompt for
24  *                            cupsDoFileRequest().
25  *   report_attr()          - Report an IPP attribute value.
26  *   report_printer_state() - Report the printer state.
27  *   run_pictwps_filter()   - Convert PICT files to PostScript when printing
28  *                            remotely.
29  *   sigterm_handler()      - Handle 'terminate' signals that stop the backend.
30  */
31
32 /*
33  * Include necessary headers.
34  */
35
36 #include <cups/http-private.h>
37 #include "backend-private.h"
38 #include <sys/types.h>
39 #include <sys/stat.h>
40 #include <sys/wait.h>
41
42 /*
43  * Globals...
44  */
45
46 static char     *password = NULL;       /* Password for device URI */
47 static int      password_tries = 0;     /* Password tries */
48 static const char *auth_info_required = "none";
49                                         /* New auth-info-required value */
50 #ifdef __APPLE__
51 static char     pstmpname[1024] = "";   /* Temporary PostScript file name */
52 #endif /* __APPLE__ */
53 static char     tmpfilename[1024] = ""; /* Temporary spool file name */
54 static int      job_cancelled = 0;      /* Job cancelled? */
55
56
57 /*
58  * Local functions...
59  */
60
61 static void     cancel_job(http_t *http, const char *uri, int id,
62                            const char *resource, const char *user, int version);
63 static void     check_printer_state(http_t *http, const char *uri,
64                                     const char *resource, const char *user,
65                                     int version, int job_id);
66 #ifdef HAVE_LIBZ
67 static void     compress_files(int num_files, char **files);
68 #endif /* HAVE_LIBZ */
69 static const char *password_cb(const char *);
70 static void     report_attr(ipp_attribute_t *attr);
71 static int      report_printer_state(ipp_t *ipp, int job_id);
72
73 #ifdef __APPLE__
74 static int      run_pictwps_filter(char **argv, const char *filename);
75 #endif /* __APPLE__ */
76 static void     sigterm_handler(int sig);
77
78
79 /*
80  * 'main()' - Send a file to the printer or server.
81  *
82  * Usage:
83  *
84  *    printer-uri job-id user title copies options [file]
85  */
86
87 int                                     /* O - Exit status */
88 main(int  argc,                         /* I - Number of command-line args */
89      char *argv[])                      /* I - Command-line arguments */
90 {
91   int           i;                      /* Looping var */
92   int           send_options;           /* Send job options? */
93   int           num_options;            /* Number of printer options */
94   cups_option_t *options;               /* Printer options */
95   const char    *device_uri;            /* Device URI */
96   char          scheme[255],            /* Scheme in URI */
97                 hostname[1024],         /* Hostname */
98                 username[255],          /* Username info */
99                 resource[1024],         /* Resource info (printer name) */
100                 addrname[256],          /* Address name */
101                 *optptr,                /* Pointer to URI options */
102                 *name,                  /* Name of option */
103                 *value,                 /* Value of option */
104                 sep;                    /* Separator character */
105   int           snmp_fd,                /* SNMP socket */
106                 start_count,            /* Page count via SNMP at start */
107                 page_count,             /* Page count via SNMP */
108                 have_supplies;          /* Printer supports supply levels? */
109   int           num_files;              /* Number of files to print */
110   char          **files,                /* Files to print */
111                 *filename;              /* Pointer to single filename */
112   int           port;                   /* Port number (not used) */
113   char          uri[HTTP_MAX_URI];      /* Updated URI without user/pass */
114   ipp_status_t  ipp_status;             /* Status of IPP request */
115   http_t        *http;                  /* HTTP connection */
116   ipp_t         *request,               /* IPP request */
117                 *response,              /* IPP response */
118                 *supported;             /* get-printer-attributes response */
119   time_t        start_time;             /* Time of first connect */
120   int           recoverable;            /* Recoverable error shown? */
121   int           contimeout;             /* Connection timeout */
122   int           delay;                  /* Delay for retries... */
123   int           compression,            /* Do compression of the job data? */
124                 waitjob,                /* Wait for job complete? */
125                 waitprinter;            /* Wait for printer ready? */
126   ipp_attribute_t *job_id_attr;         /* job-id attribute */
127   int           job_id;                 /* job-id value */
128   ipp_attribute_t *job_sheets;          /* job-media-sheets-completed */
129   ipp_attribute_t *job_state;           /* job-state */
130   ipp_attribute_t *copies_sup;          /* copies-supported */
131   ipp_attribute_t *format_sup;          /* document-format-supported */
132   ipp_attribute_t *printer_state;       /* printer-state attribute */
133   ipp_attribute_t *printer_accepting;   /* printer-is-accepting-jobs */
134   int           copies,                 /* Number of copies for job */
135                 copies_remaining;       /* Number of copies remaining */
136   const char    *content_type,          /* CONTENT_TYPE environment variable */
137                 *final_content_type;    /* FINAL_CONTENT_TYPE environment var */
138 #if defined(HAVE_SIGACTION) && !defined(HAVE_SIGSET)
139   struct sigaction action;              /* Actions for POSIX signals */
140 #endif /* HAVE_SIGACTION && !HAVE_SIGSET */
141   int           version;                /* IPP version */
142   static const char * const pattrs[] =
143                 {                       /* Printer attributes we want */
144                   "com.apple.print.recoverable-message",
145                   "copies-supported",
146                   "document-format-supported",
147                   "marker-colors",
148                   "marker-high-levels",
149                   "marker-levels",
150                   "marker-low-levels",
151                   "marker-message",
152                   "marker-names",
153                   "marker-types",
154                   "printer-is-accepting-jobs",
155                   "printer-state",
156                   "printer-state-message",
157                   "printer-state-reasons",
158                 };
159   static const char * const jattrs[] =
160                 {                       /* Job attributes we want */
161                   "job-media-sheets-completed",
162                   "job-state"
163                 };
164
165
166  /*
167   * Make sure status messages are not buffered...
168   */
169
170   setbuf(stderr, NULL);
171
172  /*
173   * Ignore SIGPIPE and catch SIGTERM signals...
174   */
175
176 #ifdef HAVE_SIGSET
177   sigset(SIGPIPE, SIG_IGN);
178   sigset(SIGTERM, sigterm_handler);
179 #elif defined(HAVE_SIGACTION)
180   memset(&action, 0, sizeof(action));
181   action.sa_handler = SIG_IGN;
182   sigaction(SIGPIPE, &action, NULL);
183
184   sigemptyset(&action.sa_mask);
185   sigaddset(&action.sa_mask, SIGTERM);
186   action.sa_handler = sigterm_handler;
187   sigaction(SIGTERM, &action, NULL);
188 #else
189   signal(SIGPIPE, SIG_IGN);
190   signal(SIGTERM, sigterm_handler);
191 #endif /* HAVE_SIGSET */
192
193  /*
194   * Check command-line...
195   */
196
197   if (argc == 1)
198   {
199     char *s;
200
201     if ((s = strrchr(argv[0], '/')) != NULL)
202       s ++;
203     else
204       s = argv[0];
205
206     printf("network %s \"Unknown\" \"%s (%s)\"\n",
207            s, _cupsLangString(cupsLangDefault(),
208                               _("Internet Printing Protocol")), s);
209     return (CUPS_BACKEND_OK);
210   }
211   else if (argc < 6)
212   {
213     _cupsLangPrintf(stderr,
214                     _("Usage: %s job-id user title copies options [file]\n"),
215                     argv[0]);
216     return (CUPS_BACKEND_STOP);
217   }
218
219  /*
220   * Get the (final) content type...
221   */
222
223   if ((content_type = getenv("CONTENT_TYPE")) == NULL)
224     content_type = "application/octet-stream";
225
226   if ((final_content_type = getenv("FINAL_CONTENT_TYPE")) == NULL)
227   {
228     final_content_type = content_type;
229
230     if (!strncmp(final_content_type, "printer/", 8))
231       final_content_type = "application/vnd.cups-raw";
232   }
233
234  /*
235   * Extract the hostname and printer name from the URI...
236   */
237
238   if ((device_uri = cupsBackendDeviceURI(argv)) == NULL)
239     return (CUPS_BACKEND_FAILED);
240
241   httpSeparateURI(HTTP_URI_CODING_ALL, device_uri, scheme, sizeof(scheme),
242                   username, sizeof(username), hostname, sizeof(hostname), &port,
243                   resource, sizeof(resource));
244
245   if (!port)
246     port = IPP_PORT;                    /* Default to port 631 */
247
248   if (!strcmp(scheme, "https"))
249     cupsSetEncryption(HTTP_ENCRYPT_ALWAYS);
250   else
251     cupsSetEncryption(HTTP_ENCRYPT_IF_REQUESTED);
252
253  /*
254   * See if there are any options...
255   */
256
257   compression = 0;
258   version     = 11;
259   waitjob     = 1;
260   waitprinter = 1;
261   contimeout  = 7 * 24 * 60 * 60;
262
263   if ((optptr = strchr(resource, '?')) != NULL)
264   {
265    /*
266     * Yup, terminate the device name string and move to the first
267     * character of the optptr...
268     */
269
270     *optptr++ = '\0';
271
272    /*
273     * Then parse the optptr...
274     */
275
276     while (*optptr)
277     {
278      /*
279       * Get the name...
280       */
281
282       name = optptr;
283
284       while (*optptr && *optptr != '=' && *optptr != '+' && *optptr != '&')
285         optptr ++;
286
287       if ((sep = *optptr) != '\0')
288         *optptr++ = '\0';
289
290       if (sep == '=')
291       {
292        /*
293         * Get the value...
294         */
295
296         value = optptr;
297
298         while (*optptr && *optptr != '+' && *optptr != '&')
299           optptr ++;
300
301         if (*optptr)
302           *optptr++ = '\0';
303       }
304       else
305         value = (char *)"";
306
307      /*
308       * Process the option...
309       */
310
311       if (!strcasecmp(name, "waitjob"))
312       {
313        /*
314         * Wait for job completion?
315         */
316
317         waitjob = !strcasecmp(value, "on") ||
318                   !strcasecmp(value, "yes") ||
319                   !strcasecmp(value, "true");
320       }
321       else if (!strcasecmp(name, "waitprinter"))
322       {
323        /*
324         * Wait for printer idle?
325         */
326
327         waitprinter = !strcasecmp(value, "on") ||
328                       !strcasecmp(value, "yes") ||
329                       !strcasecmp(value, "true");
330       }
331       else if (!strcasecmp(name, "encryption"))
332       {
333        /*
334         * Enable/disable encryption?
335         */
336
337         if (!strcasecmp(value, "always"))
338           cupsSetEncryption(HTTP_ENCRYPT_ALWAYS);
339         else if (!strcasecmp(value, "required"))
340           cupsSetEncryption(HTTP_ENCRYPT_REQUIRED);
341         else if (!strcasecmp(value, "never"))
342           cupsSetEncryption(HTTP_ENCRYPT_NEVER);
343         else if (!strcasecmp(value, "ifrequested"))
344           cupsSetEncryption(HTTP_ENCRYPT_IF_REQUESTED);
345         else
346         {
347           _cupsLangPrintf(stderr,
348                           _("ERROR: Unknown encryption option value \"%s\"!\n"),
349                           value);
350         }
351       }
352       else if (!strcasecmp(name, "version"))
353       {
354         if (!strcmp(value, "1.0"))
355           version = 10;
356         else if (!strcmp(value, "1.1"))
357           version = 11;
358         else if (!strcmp(value, "2.0"))
359           version = 20;
360         else if (!strcmp(value, "2.1"))
361           version = 21;
362         else
363         {
364           _cupsLangPrintf(stderr,
365                           _("ERROR: Unknown version option value \"%s\"!\n"),
366                           value);
367         }
368       }
369 #ifdef HAVE_LIBZ
370       else if (!strcasecmp(name, "compression"))
371       {
372         compression = !strcasecmp(value, "true") ||
373                       !strcasecmp(value, "yes") ||
374                       !strcasecmp(value, "on") ||
375                       !strcasecmp(value, "gzip");
376       }
377 #endif /* HAVE_LIBZ */
378       else if (!strcasecmp(name, "contimeout"))
379       {
380        /*
381         * Set the connection timeout...
382         */
383
384         if (atoi(value) > 0)
385           contimeout = atoi(value);
386       }
387       else
388       {
389        /*
390         * Unknown option...
391         */
392
393         _cupsLangPrintf(stderr,
394                         _("ERROR: Unknown option \"%s\" with value \"%s\"!\n"),
395                         name, value);
396       }
397     }
398   }
399
400  /*
401   * If we have 7 arguments, print the file named on the command-line.
402   * Otherwise, copy stdin to a temporary file and print the temporary
403   * file.
404   */
405
406   if (argc == 6)
407   {
408    /*
409     * Copy stdin to a temporary file...
410     */
411
412     int                 fd;             /* File descriptor */
413     http_addrlist_t     *addrlist;      /* Address list */
414     off_t               tbytes;         /* Total bytes copied */
415
416
417     fputs("STATE: +connecting-to-device\n", stderr);
418     fprintf(stderr, "DEBUG: Looking up \"%s\"...\n", hostname);
419
420     if ((addrlist = httpAddrGetList(hostname, AF_UNSPEC, "1")) == NULL)
421     {
422       _cupsLangPrintf(stderr, _("ERROR: Unable to locate printer \'%s\'!\n"),
423                       hostname);
424       return (CUPS_BACKEND_STOP);
425     }
426
427     snmp_fd = _cupsSNMPOpen(addrlist->addr.addr.sa_family);
428
429     if ((fd = cupsTempFd(tmpfilename, sizeof(tmpfilename))) < 0)
430     {
431       _cupsLangPrintError("ERROR", _("Unable to create temporary file"));
432       return (CUPS_BACKEND_FAILED);
433     }
434
435     _cupsLangPuts(stderr, _("INFO: Copying print data...\n"));
436
437     tbytes = backendRunLoop(-1, fd, snmp_fd, &(addrlist->addr), 0, 0,
438                             backendNetworkSideCB);
439
440     if (snmp_fd >= 0)
441       _cupsSNMPClose(snmp_fd);
442
443     httpAddrFreeList(addrlist);
444
445     close(fd);
446
447    /*
448     * Don't try printing files less than 2 bytes...
449     */
450
451     if (tbytes <= 1)
452     {
453       _cupsLangPuts(stderr, _("ERROR: Empty print file!\n"));
454       unlink(tmpfilename);
455       return (CUPS_BACKEND_FAILED);
456     }
457
458    /*
459     * Point to the single file from stdin...
460     */
461
462     filename     = tmpfilename;
463     num_files    = 1;
464     files        = &filename;
465     send_options = 0;
466   }
467   else
468   {
469    /*
470     * Point to the files on the command-line...
471     */
472
473     num_files    = argc - 6;
474     files        = argv + 6;
475     send_options = 1;
476
477 #ifdef HAVE_LIBZ
478     if (compression)
479       compress_files(num_files, files);
480 #endif /* HAVE_LIBZ */
481   }
482
483   fprintf(stderr, "DEBUG: %d files to send in job...\n", num_files);
484
485  /*
486   * Set the authentication info, if any...
487   */
488
489   cupsSetPasswordCB(password_cb);
490
491   if (username[0])
492   {
493    /*
494     * Use authenticaion information in the device URI...
495     */
496
497     if ((password = strchr(username, ':')) != NULL)
498       *password++ = '\0';
499
500     cupsSetUser(username);
501   }
502   else if (!getuid())
503   {
504    /*
505     * Try loading authentication information from the environment.
506     */
507
508     const char *ptr = getenv("AUTH_USERNAME");
509
510     if (ptr)
511       cupsSetUser(ptr);
512
513     password = getenv("AUTH_PASSWORD");
514   }
515
516  /*
517   * Try connecting to the remote server...
518   */
519
520   delay       = 5;
521   recoverable = 0;
522   start_time  = time(NULL);
523
524   fputs("STATE: +connecting-to-device\n", stderr);
525
526   do
527   {
528     fprintf(stderr, "DEBUG: Connecting to %s:%d\n", hostname, port);
529     _cupsLangPuts(stderr, _("INFO: Connecting to printer...\n"));
530
531     if ((http = httpConnectEncrypt(hostname, port, cupsEncryption())) == NULL)
532     {
533       if (job_cancelled)
534         break;
535
536       if (getenv("CLASS") != NULL)
537       {
538        /*
539         * If the CLASS environment variable is set, the job was submitted
540         * to a class and not to a specific queue.  In this case, we want
541         * to abort immediately so that the job can be requeued on the next
542         * available printer in the class.
543         */
544
545         _cupsLangPuts(stderr,
546                       _("INFO: Unable to contact printer, queuing on next "
547                         "printer in class...\n"));
548
549         if (tmpfilename[0])
550           unlink(tmpfilename);
551
552        /*
553         * Sleep 5 seconds to keep the job from requeuing too rapidly...
554         */
555
556         sleep(5);
557
558         return (CUPS_BACKEND_FAILED);
559       }
560
561       if (errno == ECONNREFUSED || errno == EHOSTDOWN ||
562           errno == EHOSTUNREACH)
563       {
564         if (contimeout && (time(NULL) - start_time) > contimeout)
565         {
566           _cupsLangPuts(stderr, _("ERROR: Printer not responding!\n"));
567           return (CUPS_BACKEND_FAILED);
568         }
569
570         recoverable = 1;
571
572         _cupsLangPrintf(stderr,
573                         _("WARNING: recoverable: Network host \'%s\' is busy; "
574                           "will retry in %d seconds...\n"),
575                         hostname, delay);
576
577         sleep(delay);
578
579         if (delay < 30)
580           delay += 5;
581       }
582       else if (h_errno)
583       {
584         _cupsLangPrintf(stderr, _("ERROR: Unable to locate printer \'%s\'!\n"),
585                         hostname);
586         return (CUPS_BACKEND_STOP);
587       }
588       else
589       {
590         recoverable = 1;
591
592         fprintf(stderr, "DEBUG: Connection error: %s\n", strerror(errno));
593         _cupsLangPuts(stderr,
594                       _("ERROR: recoverable: Unable to connect to printer; will "
595                         "retry in 30 seconds...\n"));
596         sleep(30);
597       }
598
599       if (job_cancelled)
600         break;
601     }
602   }
603   while (http == NULL);
604
605   if (job_cancelled || !http)
606   {
607     if (tmpfilename[0])
608       unlink(tmpfilename);
609
610     return (CUPS_BACKEND_FAILED);
611   }
612
613   fputs("STATE: -connecting-to-device\n", stderr);
614   _cupsLangPuts(stderr, _("INFO: Connected to printer...\n"));
615
616 #ifdef AF_INET6
617   if (http->hostaddr->addr.sa_family == AF_INET6)
618     fprintf(stderr, "DEBUG: Connected to [%s]:%d (IPv6)...\n",
619             httpAddrString(http->hostaddr, addrname, sizeof(addrname)),
620             ntohs(http->hostaddr->ipv6.sin6_port));
621   else
622 #endif /* AF_INET6 */
623     if (http->hostaddr->addr.sa_family == AF_INET)
624       fprintf(stderr, "DEBUG: Connected to %s:%d (IPv4)...\n",
625               httpAddrString(http->hostaddr, addrname, sizeof(addrname)),
626               ntohs(http->hostaddr->ipv4.sin_port));
627
628  /*
629   * See if the printer supports SNMP...
630   */
631
632   if ((snmp_fd = _cupsSNMPOpen(http->hostaddr->addr.sa_family)) >= 0)
633     have_supplies = !backendSNMPSupplies(snmp_fd, http->hostaddr, &start_count,
634                                          NULL);
635   else
636     have_supplies = start_count = 0;
637
638  /*
639   * Build a URI for the printer and fill the standard IPP attributes for
640   * an IPP_PRINT_FILE request.  We can't use the URI in argv[0] because it
641   * might contain username:password information...
642   */
643
644   httpAssembleURI(HTTP_URI_CODING_ALL, uri, sizeof(uri), scheme, NULL, hostname,
645                   port, resource);
646
647  /*
648   * First validate the destination and see if the device supports multiple
649   * copies.  We have to do this because some IPP servers (e.g. HP JetDirect)
650   * don't support the copies attribute...
651   */
652
653   copies_sup = NULL;
654   format_sup = NULL;
655   supported  = NULL;
656
657   do
658   {
659    /*
660     * Check for side-channel requests...
661     */
662
663     backendCheckSideChannel(snmp_fd, http->hostaddr);
664
665    /*
666     * Build the IPP request...
667     */
668
669     request = ippNewRequest(IPP_GET_PRINTER_ATTRIBUTES);
670     request->request.op.version[0] = version / 10;
671     request->request.op.version[1] = version % 10;
672
673     ippAddString(request, IPP_TAG_OPERATION, IPP_TAG_URI, "printer-uri",
674                  NULL, uri);
675
676     ippAddStrings(request, IPP_TAG_OPERATION, IPP_TAG_KEYWORD,
677                   "requested-attributes", sizeof(pattrs) / sizeof(pattrs[0]),
678                   NULL, pattrs);
679
680    /*
681     * Do the request...
682     */
683
684     fputs("DEBUG: Getting supported attributes...\n", stderr);
685
686     if (http->version < HTTP_1_1)
687       httpReconnect(http);
688
689     if ((supported = cupsDoRequest(http, request, resource)) == NULL)
690       ipp_status = cupsLastError();
691     else
692       ipp_status = supported->request.status.status_code;
693
694     if (ipp_status > IPP_OK_CONFLICT)
695     {
696       if (ipp_status == IPP_PRINTER_BUSY ||
697           ipp_status == IPP_SERVICE_UNAVAILABLE)
698       {
699         if (contimeout && (time(NULL) - start_time) > contimeout)
700         {
701           _cupsLangPuts(stderr, _("ERROR: Printer not responding!\n"));
702           return (CUPS_BACKEND_FAILED);
703         }
704
705         recoverable = 1;
706
707         _cupsLangPrintf(stderr,
708                         _("WARNING: recoverable: Network host \'%s\' is busy; "
709                           "will retry in %d seconds...\n"),
710                         hostname, delay);
711
712         report_printer_state(supported, 0);
713
714         sleep(delay);
715
716         if (delay < 30)
717           delay += 5;
718       }
719       else if ((ipp_status == IPP_BAD_REQUEST ||
720                 ipp_status == IPP_VERSION_NOT_SUPPORTED) && version > 10)
721       {
722        /*
723         * Switch to IPP/1.0...
724         */
725
726         _cupsLangPrintf(stderr,
727                         _("INFO: Printer does not support IPP/%d.%d, trying "
728                           "IPP/1.0...\n"), version / 10, version % 10);
729         version = 10;
730         httpReconnect(http);
731       }
732       else if (ipp_status == IPP_NOT_FOUND)
733       {
734         _cupsLangPuts(stderr, _("ERROR: Destination printer does not exist!\n"));
735
736         if (supported)
737           ippDelete(supported);
738
739         return (CUPS_BACKEND_STOP);
740       }
741       else if (ipp_status == IPP_NOT_AUTHORIZED || ipp_status == IPP_FORBIDDEN)
742       {
743         if (!strncmp(httpGetField(http, HTTP_FIELD_WWW_AUTHENTICATE),
744                      "Negotiate", 9))
745           auth_info_required = "negotiate";
746
747         fprintf(stderr, "ATTR: auth-info-required=%s\n", auth_info_required);
748         return (CUPS_BACKEND_AUTH_REQUIRED);
749       }
750       else
751       {
752         _cupsLangPrintf(stderr,
753                         _("ERROR: Unable to get printer status (%s)!\n"),
754                         cupsLastErrorString());
755         sleep(10);
756       }
757
758       if (supported)
759         ippDelete(supported);
760
761       continue;
762     }
763     else if ((copies_sup = ippFindAttribute(supported, "copies-supported",
764                                             IPP_TAG_RANGE)) != NULL)
765     {
766      /*
767       * Has the "copies-supported" attribute - does it have an upper
768       * bound > 1?
769       */
770
771       if (copies_sup->values[0].range.upper <= 1)
772         copies_sup = NULL; /* No */
773     }
774
775     format_sup = ippFindAttribute(supported, "document-format-supported",
776                                   IPP_TAG_MIMETYPE);
777
778     if (format_sup)
779     {
780       fprintf(stderr, "DEBUG: document-format-supported (%d values)\n",
781               format_sup->num_values);
782       for (i = 0; i < format_sup->num_values; i ++)
783         fprintf(stderr, "DEBUG: [%d] = \"%s\"\n", i,
784                 format_sup->values[i].string.text);
785     }
786
787     report_printer_state(supported, 0);
788   }
789   while (ipp_status > IPP_OK_CONFLICT);
790
791  /*
792   * See if the printer is accepting jobs and is not stopped; if either
793   * condition is true and we are printing to a class, requeue the job...
794   */
795
796   if (getenv("CLASS") != NULL)
797   {
798     printer_state     = ippFindAttribute(supported, "printer-state",
799                                          IPP_TAG_ENUM);
800     printer_accepting = ippFindAttribute(supported, "printer-is-accepting-jobs",
801                                          IPP_TAG_BOOLEAN);
802
803     if (printer_state == NULL ||
804         (printer_state->values[0].integer > IPP_PRINTER_PROCESSING &&
805          waitprinter) ||
806         printer_accepting == NULL ||
807         !printer_accepting->values[0].boolean)
808     {
809      /*
810       * If the CLASS environment variable is set, the job was submitted
811       * to a class and not to a specific queue.  In this case, we want
812       * to abort immediately so that the job can be requeued on the next
813       * available printer in the class.
814       */
815
816       _cupsLangPuts(stderr,
817                     _("INFO: Unable to contact printer, queuing on next "
818                       "printer in class...\n"));
819
820       ippDelete(supported);
821       httpClose(http);
822
823       if (tmpfilename[0])
824         unlink(tmpfilename);
825
826      /*
827       * Sleep 5 seconds to keep the job from requeuing too rapidly...
828       */
829
830       sleep(5);
831
832       return (CUPS_BACKEND_FAILED);
833     }
834   }
835
836   if (recoverable)
837   {
838    /*
839     * If we've shown a recoverable error make sure the printer proxies
840     * have a chance to see the recovered message. Not pretty but
841     * necessary for now...
842     */
843
844     fputs("INFO: recovered: \n", stderr);
845     sleep(5);
846   }
847
848  /*
849   * See if the printer supports multiple copies...
850   */
851
852   copies = atoi(argv[4]);
853
854   if (copies_sup || argc < 7)
855   {
856     copies_remaining = 1;
857
858     if (argc < 7)
859       copies = 1;
860   }
861   else
862     copies_remaining = copies;
863
864  /*
865   * Then issue the print-job request...
866   */
867
868   job_id  = 0;
869
870   while (copies_remaining > 0)
871   {
872    /*
873     * Check for side-channel requests...
874     */
875
876     backendCheckSideChannel(snmp_fd, http->hostaddr);
877
878    /*
879     * Build the IPP request...
880     */
881
882     if (job_cancelled)
883       break;
884
885     if (num_files > 1)
886       request = ippNewRequest(IPP_CREATE_JOB);
887     else
888       request = ippNewRequest(IPP_PRINT_JOB);
889
890     request->request.op.version[0] = version / 10;
891     request->request.op.version[1] = version % 10;
892
893     ippAddString(request, IPP_TAG_OPERATION, IPP_TAG_URI, "printer-uri",
894                  NULL, uri);
895
896     fprintf(stderr, "DEBUG: printer-uri = \"%s\"\n", uri);
897
898     if (argv[2][0])
899       ippAddString(request, IPP_TAG_OPERATION, IPP_TAG_NAME,
900                    "requesting-user-name", NULL, argv[2]);
901
902     fprintf(stderr, "DEBUG: requesting-user-name = \"%s\"\n", argv[2]);
903
904    /*
905     * Only add a "job-name" attribute if the remote server supports
906     * copy generation - some IPP implementations like HP's don't seem
907     * to like UTF-8 job names (STR #1837)...
908     */
909
910     if (argv[3][0] && copies_sup)
911       ippAddString(request, IPP_TAG_OPERATION, IPP_TAG_NAME, "job-name", NULL,
912                    argv[3]);
913
914     fprintf(stderr, "DEBUG: job-name = \"%s\"\n", argv[3]);
915
916 #ifdef HAVE_LIBZ
917     if (compression)
918       ippAddString(request, IPP_TAG_OPERATION, IPP_TAG_KEYWORD,
919                    "compression", NULL, "gzip");
920 #endif /* HAVE_LIBZ */
921
922    /*
923     * Handle options on the command-line...
924     */
925
926     options     = NULL;
927     num_options = cupsParseOptions(argv[5], 0, &options);
928
929 #ifdef __APPLE__
930     if (!strcasecmp(final_content_type, "application/pictwps") &&
931         num_files == 1)
932     {
933       if (format_sup != NULL)
934       {
935         for (i = 0; i < format_sup->num_values; i ++)
936           if (!strcasecmp(final_content_type, format_sup->values[i].string.text))
937             break;
938       }
939
940       if (format_sup == NULL || i >= format_sup->num_values)
941       {
942        /*
943         * Remote doesn't support "application/pictwps" (i.e. it's not MacOS X)
944         * so convert the document to PostScript...
945         */
946
947         if (run_pictwps_filter(argv, files[0]))
948         {
949           if (pstmpname[0])
950             unlink(pstmpname);
951
952           if (tmpfilename[0])
953             unlink(tmpfilename);
954
955           return (CUPS_BACKEND_FAILED);
956         }
957
958         files[0] = pstmpname;
959
960        /*
961         * Change the MIME type to application/postscript and change the
962         * number of copies to 1...
963         */
964
965         final_content_type = "application/postscript";
966         copies             = 1;
967         copies_remaining   = 1;
968         send_options       = 0;
969       }
970     }
971 #endif /* __APPLE__ */
972
973     if (format_sup != NULL)
974     {
975       for (i = 0; i < format_sup->num_values; i ++)
976         if (!strcasecmp(final_content_type, format_sup->values[i].string.text))
977           break;
978
979       if (i < format_sup->num_values)
980         ippAddString(request, IPP_TAG_OPERATION, IPP_TAG_MIMETYPE,
981                      "document-format", NULL, final_content_type);
982     }
983
984     if (copies_sup && version > 10 && send_options)
985     {
986      /*
987       * Only send options if the destination printer supports the copies
988       * attribute and IPP/1.1.  This is a hack for the HP and Lexmark
989       * implementations of IPP, which do not accept extension attributes
990       * and incorrectly report a client-error-bad-request error instead of
991       * the successful-ok-unsupported-attributes status.  In short, at least
992       * some HP and Lexmark implementations of IPP are non-compliant.
993       */
994
995       cupsEncodeOptions(request, num_options, options);
996
997       ippAddInteger(request, IPP_TAG_JOB, IPP_TAG_INTEGER, "copies",
998                     copies);
999     }
1000
1001     cupsFreeOptions(num_options, options);
1002
1003    /*
1004     * If copies aren't supported, then we are likely dealing with an HP
1005     * JetDirect.  The HP IPP implementation seems to close the connection
1006     * after every request - that is, it does *not* implement HTTP Keep-
1007     * Alive, which is REQUIRED by HTTP/1.1...
1008     */
1009
1010     if (!copies_sup)
1011       httpReconnect(http);
1012
1013    /*
1014     * Do the request...
1015     */
1016
1017     if (http->version < HTTP_1_1)
1018       httpReconnect(http);
1019
1020     if (num_files > 1)
1021       response = cupsDoRequest(http, request, resource);
1022     else
1023       response = cupsDoFileRequest(http, request, resource, files[0]);
1024
1025     ipp_status = cupsLastError();
1026
1027     if (ipp_status > IPP_OK_CONFLICT)
1028     {
1029       job_id = 0;
1030
1031       if (job_cancelled)
1032         break;
1033
1034       if (ipp_status == IPP_SERVICE_UNAVAILABLE ||
1035           ipp_status == IPP_PRINTER_BUSY)
1036       {
1037         _cupsLangPuts(stderr,
1038                       _("INFO: Printer busy; will retry in 10 seconds...\n"));
1039         sleep(10);
1040       }
1041       else if ((ipp_status == IPP_BAD_REQUEST ||
1042                 ipp_status == IPP_VERSION_NOT_SUPPORTED) && version > 10)
1043       {
1044        /*
1045         * Switch to IPP/1.0...
1046         */
1047
1048         _cupsLangPrintf(stderr,
1049                         _("INFO: Printer does not support IPP/%d.%d, trying "
1050                           "IPP/1.0...\n"), version / 10, version % 10);
1051         version = 10;
1052         httpReconnect(http);
1053       }
1054       else
1055       {
1056        /*
1057         * Update auth-info-required as needed...
1058         */
1059
1060         _cupsLangPrintf(stderr, _("ERROR: Print file was not accepted (%s)!\n"),
1061                         cupsLastErrorString());
1062
1063         if (ipp_status == IPP_NOT_AUTHORIZED || ipp_status == IPP_FORBIDDEN)
1064         {
1065           fprintf(stderr, "DEBUG: WWW-Authenticate=\"%s\"\n",
1066                   httpGetField(http, HTTP_FIELD_WWW_AUTHENTICATE));
1067
1068          /*
1069           * Normal authentication goes through the password callback, which sets
1070           * auth_info_required to "username,password".  Kerberos goes directly
1071           * through GSSAPI, so look for Negotiate in the WWW-Authenticate header
1072           * here and set auth_info_required as needed...
1073           */
1074
1075           if (!strncmp(httpGetField(http, HTTP_FIELD_WWW_AUTHENTICATE),
1076                        "Negotiate", 9))
1077             auth_info_required = "negotiate";
1078         }
1079       }
1080     }
1081     else if ((job_id_attr = ippFindAttribute(response, "job-id",
1082                                              IPP_TAG_INTEGER)) == NULL)
1083     {
1084       _cupsLangPuts(stderr,
1085                     _("NOTICE: Print file accepted - job ID unknown.\n"));
1086       job_id = 0;
1087     }
1088     else
1089     {
1090       job_id = job_id_attr->values[0].integer;
1091       _cupsLangPrintf(stderr, _("NOTICE: Print file accepted - job ID %d.\n"),
1092                       job_id);
1093     }
1094
1095     ippDelete(response);
1096
1097     if (job_cancelled)
1098       break;
1099
1100     if (job_id && num_files > 1)
1101     {
1102       for (i = 0; i < num_files; i ++)
1103       {
1104        /*
1105         * Check for side-channel requests...
1106         */
1107
1108         backendCheckSideChannel(snmp_fd, http->hostaddr);
1109
1110        /*
1111         * Send the next file in the job...
1112         */
1113
1114         request = ippNewRequest(IPP_SEND_DOCUMENT);
1115         request->request.op.version[0] = version / 10;
1116         request->request.op.version[1] = version % 10;
1117
1118         ippAddString(request, IPP_TAG_OPERATION, IPP_TAG_URI, "printer-uri",
1119                      NULL, uri);
1120
1121         ippAddInteger(request, IPP_TAG_OPERATION, IPP_TAG_INTEGER, "job-id",
1122                       job_id);
1123
1124         if (argv[2][0])
1125           ippAddString(request, IPP_TAG_OPERATION, IPP_TAG_NAME,
1126                        "requesting-user-name", NULL, argv[2]);
1127
1128         if ((i + 1) == num_files)
1129           ippAddBoolean(request, IPP_TAG_OPERATION, "last-document", 1);
1130
1131         ippAddString(request, IPP_TAG_OPERATION, IPP_TAG_MIMETYPE,
1132                      "document-format", NULL, content_type);
1133
1134         if (http->version < HTTP_1_1)
1135           httpReconnect(http);
1136
1137         ippDelete(cupsDoFileRequest(http, request, resource, files[i]));
1138
1139         if (cupsLastError() > IPP_OK_CONFLICT)
1140         {
1141           ipp_status = cupsLastError();
1142
1143           _cupsLangPrintf(stderr,
1144                           _("ERROR: Unable to add file %d to job: %s\n"),
1145                           job_id, cupsLastErrorString());
1146           break;
1147         }
1148       }
1149     }
1150
1151     if (ipp_status <= IPP_OK_CONFLICT && argc > 6)
1152     {
1153       fprintf(stderr, "PAGE: 1 %d\n", copies_sup ? atoi(argv[4]) : 1);
1154       copies_remaining --;
1155     }
1156     else if (ipp_status == IPP_SERVICE_UNAVAILABLE ||
1157              ipp_status == IPP_PRINTER_BUSY)
1158       continue;
1159     else
1160       copies_remaining --;
1161
1162    /*
1163     * Wait for the job to complete...
1164     */
1165
1166     if (!job_id || !waitjob)
1167       continue;
1168
1169     _cupsLangPuts(stderr, _("INFO: Waiting for job to complete...\n"));
1170
1171     for (delay = 1; !job_cancelled;)
1172     {
1173      /*
1174       * Check for side-channel requests...
1175       */
1176
1177       backendCheckSideChannel(snmp_fd, http->hostaddr);
1178
1179      /*
1180       * Build an IPP_GET_JOB_ATTRIBUTES request...
1181       */
1182
1183       request = ippNewRequest(IPP_GET_JOB_ATTRIBUTES);
1184       request->request.op.version[0] = version / 10;
1185       request->request.op.version[1] = version % 10;
1186
1187       ippAddString(request, IPP_TAG_OPERATION, IPP_TAG_URI, "printer-uri",
1188                    NULL, uri);
1189
1190       ippAddInteger(request, IPP_TAG_OPERATION, IPP_TAG_INTEGER, "job-id",
1191                     job_id);
1192
1193       if (argv[2][0])
1194         ippAddString(request, IPP_TAG_OPERATION, IPP_TAG_NAME,
1195                      "requesting-user-name", NULL, argv[2]);
1196
1197       ippAddStrings(request, IPP_TAG_OPERATION, IPP_TAG_KEYWORD,
1198                     "requested-attributes", sizeof(jattrs) / sizeof(jattrs[0]),
1199                     NULL, jattrs);
1200
1201      /*
1202       * Do the request...
1203       */
1204
1205       if (!copies_sup || http->version < HTTP_1_1)
1206         httpReconnect(http);
1207
1208       response   = cupsDoRequest(http, request, resource);
1209       ipp_status = cupsLastError();
1210
1211       if (ipp_status == IPP_NOT_FOUND)
1212       {
1213        /*
1214         * Job has gone away and/or the server has no job history...
1215         */
1216
1217         ippDelete(response);
1218
1219         ipp_status = IPP_OK;
1220         break;
1221       }
1222
1223       if (ipp_status > IPP_OK_CONFLICT)
1224       {
1225         if (ipp_status != IPP_SERVICE_UNAVAILABLE &&
1226             ipp_status != IPP_PRINTER_BUSY)
1227         {
1228           ippDelete(response);
1229
1230           _cupsLangPrintf(stderr,
1231                           _("ERROR: Unable to get job %d attributes (%s)!\n"),
1232                           job_id, cupsLastErrorString());
1233           break;
1234         }
1235       }
1236
1237       if (response)
1238       {
1239         if ((job_state = ippFindAttribute(response, "job-state",
1240                                           IPP_TAG_ENUM)) != NULL)
1241         {
1242          /*
1243           * Stop polling if the job is finished or pending-held...
1244           */
1245
1246           if (job_state->values[0].integer > IPP_JOB_STOPPED)
1247           {
1248             if ((job_sheets = ippFindAttribute(response, 
1249                                                "job-media-sheets-completed",
1250                                                IPP_TAG_INTEGER)) != NULL)
1251               fprintf(stderr, "PAGE: total %d\n",
1252                       job_sheets->values[0].integer);
1253
1254             ippDelete(response);
1255             break;
1256           }
1257         }
1258         else
1259         {
1260          /*
1261           * If the printer does not return a job-state attribute, it does not
1262           * conform to the IPP specification - break out immediately and fail
1263           * the job...
1264           */
1265
1266           fputs("DEBUG: No job-state available from printer - stopping queue.\n",
1267                 stderr);
1268           ipp_status = IPP_INTERNAL_ERROR;
1269           break;
1270         }
1271       }
1272
1273       ippDelete(response);
1274
1275      /*
1276       * Check the printer state and report it if necessary...
1277       */
1278
1279       check_printer_state(http, uri, resource, argv[2], version, job_id);
1280
1281      /*
1282       * Wait 1-10 seconds before polling again...
1283       */
1284
1285       sleep(delay);
1286
1287       delay ++;
1288       if (delay > 10)
1289         delay = 1;
1290     }
1291   }
1292
1293  /*
1294   * Cancel the job as needed...
1295   */
1296
1297   if (job_cancelled && job_id)
1298     cancel_job(http, uri, job_id, resource, argv[2], version);
1299
1300  /*
1301   * Check the printer state and report it if necessary...
1302   */
1303
1304   check_printer_state(http, uri, resource, argv[2], version, job_id);
1305
1306  /*
1307   * Collect the final page count as needed...
1308   */
1309
1310   if (have_supplies && 
1311       !backendSNMPSupplies(snmp_fd, http->hostaddr, &page_count, NULL) &&
1312       page_count > start_count)
1313     fprintf(stderr, "PAGE: total %d\n", page_count - start_count);
1314
1315 #ifdef HAVE_GSSAPI
1316  /*
1317   * See if we used Kerberos at all...
1318   */
1319
1320   if (http->gssctx)
1321     auth_info_required = "negotiate";
1322 #endif /* HAVE_GSSAPI */
1323
1324  /*
1325   * Free memory...
1326   */
1327
1328   httpClose(http);
1329
1330   ippDelete(supported);
1331
1332  /*
1333   * Remove the temporary file(s) if necessary...
1334   */
1335
1336   if (tmpfilename[0])
1337     unlink(tmpfilename);
1338
1339 #ifdef HAVE_LIBZ
1340   if (compression)
1341   {
1342     for (i = 0; i < num_files; i ++)
1343       unlink(files[i]);
1344   }
1345 #endif /* HAVE_LIBZ */
1346
1347 #ifdef __APPLE__
1348   if (pstmpname[0])
1349     unlink(pstmpname);
1350 #endif /* __APPLE__ */
1351
1352  /*
1353   * Return the queue status...
1354   */
1355
1356   fprintf(stderr, "ATTR: auth-info-required=%s\n", auth_info_required);
1357
1358   if (ipp_status == IPP_NOT_AUTHORIZED || ipp_status == IPP_FORBIDDEN)
1359     return (CUPS_BACKEND_AUTH_REQUIRED);
1360   else if (ipp_status == IPP_INTERNAL_ERROR)
1361     return (CUPS_BACKEND_STOP);
1362   else if (ipp_status > IPP_OK_CONFLICT)
1363     return (CUPS_BACKEND_FAILED);
1364   else
1365   {
1366     _cupsLangPuts(stderr, _("INFO: Ready to print.\n"));
1367     return (CUPS_BACKEND_OK);
1368   }
1369 }
1370
1371
1372 /*
1373  * 'cancel_job()' - Cancel a print job.
1374  */
1375
1376 static void
1377 cancel_job(http_t     *http,            /* I - HTTP connection */
1378            const char *uri,             /* I - printer-uri */
1379            int        id,               /* I - job-id */
1380            const char *resource,        /* I - Resource path */
1381            const char *user,            /* I - requesting-user-name */
1382            int        version)          /* I - IPP version */
1383 {
1384   ipp_t *request;                       /* Cancel-Job request */
1385
1386
1387   _cupsLangPuts(stderr, _("INFO: Canceling print job...\n"));
1388
1389   request = ippNewRequest(IPP_CANCEL_JOB);
1390   request->request.op.version[0] = version / 10;
1391   request->request.op.version[1] = version % 10;
1392
1393   ippAddString(request, IPP_TAG_OPERATION, IPP_TAG_URI, "printer-uri",
1394                NULL, uri);
1395   ippAddInteger(request, IPP_TAG_OPERATION, IPP_TAG_INTEGER, "job-id", id);
1396
1397   if (user && user[0])
1398     ippAddString(request, IPP_TAG_OPERATION, IPP_TAG_NAME,
1399                  "requesting-user-name", NULL, user);
1400
1401  /*
1402   * Do the request...
1403   */
1404
1405   if (http->version < HTTP_1_1)
1406     httpReconnect(http);
1407
1408   ippDelete(cupsDoRequest(http, request, resource));
1409
1410   if (cupsLastError() > IPP_OK_CONFLICT)
1411     _cupsLangPrintf(stderr, _("ERROR: Unable to cancel job %d: %s\n"), id,
1412                     cupsLastErrorString());
1413 }
1414
1415
1416 /*
1417  * 'check_printer_state()' - Check the printer state...
1418  */
1419
1420 static void
1421 check_printer_state(
1422     http_t      *http,                  /* I - HTTP connection */
1423     const char  *uri,                   /* I - Printer URI */
1424     const char  *resource,              /* I - Resource path */
1425     const char  *user,                  /* I - Username, if any */
1426     int         version,                /* I - IPP version */
1427     int         job_id)                 /* I - Current job ID */
1428 {
1429   ipp_t *request,                       /* IPP request */
1430         *response;                      /* IPP response */
1431   static const char * const attrs[] =   /* Attributes we want */
1432   {
1433     "com.apple.print.recoverable-message",
1434     "marker-colors",
1435     "marker-levels",
1436     "marker-message",
1437     "marker-names",
1438     "marker-types",
1439     "printer-state-message",
1440     "printer-state-reasons"
1441   };
1442
1443
1444  /*
1445   * Check on the printer state...
1446   */
1447
1448   request = ippNewRequest(IPP_GET_PRINTER_ATTRIBUTES);
1449   request->request.op.version[0] = version / 10;
1450   request->request.op.version[1] = version % 10;
1451
1452   ippAddString(request, IPP_TAG_OPERATION, IPP_TAG_URI, "printer-uri",
1453                NULL, uri);
1454
1455   if (user && user[0])
1456     ippAddString(request, IPP_TAG_OPERATION, IPP_TAG_NAME,
1457                  "requesting-user-name", NULL, user);
1458
1459   ippAddStrings(request, IPP_TAG_OPERATION, IPP_TAG_KEYWORD,
1460                 "requested-attributes",
1461                 (int)(sizeof(attrs) / sizeof(attrs[0])), NULL, attrs);
1462
1463  /*
1464   * Do the request...
1465   */
1466
1467   if (http->version < HTTP_1_1)
1468     httpReconnect(http);
1469
1470   if ((response = cupsDoRequest(http, request, resource)) != NULL)
1471   {
1472     report_printer_state(response, job_id);
1473     ippDelete(response);
1474   }
1475 }
1476
1477
1478 #ifdef HAVE_LIBZ
1479 /*
1480  * 'compress_files()' - Compress print files...
1481  */
1482
1483 static void
1484 compress_files(int  num_files,          /* I - Number of files */
1485                char **files)            /* I - Files */
1486 {
1487   int           i,                      /* Looping var */
1488                 fd;                     /* Temporary file descriptor */
1489   ssize_t       bytes;                  /* Bytes read/written */
1490   size_t        total;                  /* Total bytes read */
1491   cups_file_t   *in,                    /* Input file */
1492                 *out;                   /* Output file */
1493   struct stat   outinfo;                /* Output file information */
1494   char          filename[1024],         /* Temporary filename */
1495                 buffer[32768];          /* Copy buffer */
1496
1497
1498   fprintf(stderr, "DEBUG: Compressing %d job files...\n", num_files);
1499   for (i = 0; i < num_files; i ++)
1500   {
1501     if ((fd = cupsTempFd(filename, sizeof(filename))) < 0)
1502     {
1503       _cupsLangPrintf(stderr,
1504                       _("ERROR: Unable to create temporary compressed print "
1505                         "file: %s\n"), strerror(errno));
1506       exit(CUPS_BACKEND_FAILED);
1507     }
1508
1509     if ((out = cupsFileOpenFd(fd, "w9")) == NULL)
1510     {
1511       _cupsLangPrintf(stderr,
1512                       _("ERROR: Unable to open temporary compressed print "
1513                         "file: %s\n"), strerror(errno));
1514       exit(CUPS_BACKEND_FAILED);
1515     }
1516
1517     if ((in = cupsFileOpen(files[i], "r")) == NULL)
1518     {
1519       _cupsLangPrintf(stderr,
1520                       _("ERROR: Unable to open print file \"%s\": %s\n"),
1521                       files[i], strerror(errno));
1522       cupsFileClose(out);
1523       exit(CUPS_BACKEND_FAILED);
1524     }
1525
1526     total = 0;
1527     while ((bytes = cupsFileRead(in, buffer, sizeof(buffer))) > 0)
1528       if (cupsFileWrite(out, buffer, bytes) < bytes)
1529       {
1530         _cupsLangPrintf(stderr,
1531                         _("ERROR: Unable to write %d bytes to \"%s\": %s\n"),
1532                         (int)bytes, filename, strerror(errno));
1533         cupsFileClose(in);
1534         cupsFileClose(out);
1535         exit(CUPS_BACKEND_FAILED);
1536       }
1537       else
1538         total += bytes;
1539
1540     cupsFileClose(out);
1541     cupsFileClose(in);
1542
1543     files[i] = strdup(filename);
1544
1545     if (!stat(filename, &outinfo))
1546       fprintf(stderr,
1547               "DEBUG: File %d compressed to %.1f%% of original size, "
1548               CUPS_LLFMT " bytes...\n",
1549               i + 1, 100.0 * outinfo.st_size / total,
1550               CUPS_LLCAST outinfo.st_size);
1551   }
1552 }
1553 #endif /* HAVE_LIBZ */
1554
1555
1556 /*
1557  * 'password_cb()' - Disable the password prompt for cupsDoFileRequest().
1558  */
1559
1560 static const char *                     /* O - Password  */
1561 password_cb(const char *prompt)         /* I - Prompt (not used) */
1562 {
1563   (void)prompt;
1564
1565  /*
1566   * Remember that we need to authenticate...
1567   */
1568
1569   auth_info_required = "username,password";
1570
1571   if (password && *password && password_tries < 3)
1572   {
1573     password_tries ++;
1574
1575     return (password);
1576   }
1577   else
1578   {
1579    /*
1580     * Give up after 3 tries or if we don't have a password to begin with...
1581     */
1582
1583     return (NULL);
1584   }
1585 }
1586
1587
1588 /*
1589  * 'report_attr()' - Report an IPP attribute value.
1590  */
1591
1592 static void
1593 report_attr(ipp_attribute_t *attr)      /* I - Attribute */
1594 {
1595   int   i;                              /* Looping var */
1596   char  value[1024],                    /* Value string */
1597         *valptr,                        /* Pointer into value string */
1598         *attrptr;                       /* Pointer into attribute value */
1599
1600
1601  /*
1602   * Convert the attribute values into quoted strings...
1603   */
1604
1605   for (i = 0, valptr = value;
1606        i < attr->num_values && valptr < (value + sizeof(value) - 10);
1607        i ++)
1608   {
1609     if (i > 0)
1610       *valptr++ = ',';
1611
1612     switch (attr->value_tag)
1613     {
1614       case IPP_TAG_INTEGER :
1615       case IPP_TAG_ENUM :
1616           snprintf(valptr, sizeof(value) - (valptr - value), "%d",
1617                    attr->values[i].integer);
1618           valptr += strlen(valptr);
1619           break;
1620
1621       case IPP_TAG_TEXT :
1622       case IPP_TAG_NAME :
1623       case IPP_TAG_KEYWORD :
1624           *valptr++ = '\"';
1625           for (attrptr = attr->values[i].string.text;
1626                *attrptr && valptr < (value + sizeof(value) - 10);
1627                attrptr ++)
1628           {
1629             if (*attrptr == '\\' || *attrptr == '\"')
1630               *valptr++ = '\\';
1631
1632             *valptr++ = *attrptr;
1633           }
1634           *valptr++ = '\"';
1635           break;
1636
1637       default :
1638          /*
1639           * Unsupported value type...
1640           */
1641
1642           return;
1643     }
1644   }
1645
1646   *valptr = '\0';
1647
1648  /*
1649   * Tell the scheduler about the new values...
1650   */
1651
1652   fprintf(stderr, "ATTR: %s=%s\n", attr->name, value);
1653 }
1654
1655
1656 /*
1657  * 'report_printer_state()' - Report the printer state.
1658  */
1659
1660 static int                              /* O - Number of reasons shown */
1661 report_printer_state(ipp_t *ipp,        /* I - IPP response */
1662                      int   job_id)      /* I - Current job ID */
1663 {
1664   int                   i;              /* Looping var */
1665   int                   count;          /* Count of reasons shown... */
1666   ipp_attribute_t       *caprm,         /* com.apple.print.recoverable-message */
1667                         *psm,           /* printer-state-message */
1668                         *reasons,       /* printer-state-reasons */
1669                         *marker;        /* marker-* attributes */
1670   const char            *reason;        /* Current reason */
1671   const char            *prefix;        /* Prefix for STATE: line */
1672   char                  state[1024];    /* State string */
1673   int                   saw_caprw;      /* Saw com.apple.print.recoverable-warning state */
1674
1675
1676   if ((psm = ippFindAttribute(ipp, "printer-state-message",
1677                               IPP_TAG_TEXT)) != NULL)
1678     fprintf(stderr, "INFO: %s\n", psm->values[0].string.text);
1679
1680   if ((reasons = ippFindAttribute(ipp, "printer-state-reasons",
1681                                   IPP_TAG_KEYWORD)) == NULL)
1682     return (0);
1683
1684   saw_caprw = 0;
1685   state[0]  = '\0';
1686   prefix    = "STATE: ";
1687
1688   for (i = 0, count = 0; i < reasons->num_values; i ++)
1689   {
1690     reason = reasons->values[i].string.text;
1691
1692     if (!strcmp(reason, "com.apple.print.recoverable-warning"))
1693       saw_caprw = 1;
1694     else if (strcmp(reason, "paused"))
1695     {
1696       strlcat(state, prefix, sizeof(state));
1697       strlcat(state, reason, sizeof(state));
1698
1699       prefix  = ",";
1700     }
1701   }
1702
1703   if (state[0])
1704     fprintf(stderr, "%s\n", state);
1705
1706  /*
1707   * Relay com.apple.print.recoverable-message...
1708   */
1709
1710   if ((caprm = ippFindAttribute(ipp, "com.apple.print.recoverable-message",
1711                                 IPP_TAG_TEXT)) != NULL)
1712     fprintf(stderr, "WARNING: %s: %s\n",
1713             saw_caprw ? "recoverable" : "recovered",
1714             caprm->values[0].string.text);
1715
1716  /*
1717   * Relay the current marker-* attribute values...
1718   */
1719
1720   if ((marker = ippFindAttribute(ipp, "marker-colors", IPP_TAG_NAME)) != NULL)
1721     report_attr(marker);
1722   if ((marker = ippFindAttribute(ipp, "marker-high-levels",
1723                                  IPP_TAG_INTEGER)) != NULL)
1724     report_attr(marker);
1725   if ((marker = ippFindAttribute(ipp, "marker-levels",
1726                                  IPP_TAG_INTEGER)) != NULL)
1727     report_attr(marker);
1728   if ((marker = ippFindAttribute(ipp, "marker-low-levels",
1729                                  IPP_TAG_INTEGER)) != NULL)
1730     report_attr(marker);
1731   if ((marker = ippFindAttribute(ipp, "marker-message", IPP_TAG_TEXT)) != NULL)
1732     report_attr(marker);
1733   if ((marker = ippFindAttribute(ipp, "marker-names", IPP_TAG_NAME)) != NULL)
1734     report_attr(marker);
1735   if ((marker = ippFindAttribute(ipp, "marker-types", IPP_TAG_KEYWORD)) != NULL)
1736     report_attr(marker);
1737
1738   return (count);
1739 }
1740
1741
1742 #ifdef __APPLE__
1743 /*
1744  * 'run_pictwps_filter()' - Convert PICT files to PostScript when printing
1745  *                          remotely.
1746  *
1747  * This step is required because the PICT format is not documented and
1748  * subject to change, so developing a filter for other OS's is infeasible.
1749  * Also, fonts required by the PICT file need to be embedded on the
1750  * client side (which has the fonts), so we run the filter to get a
1751  * PostScript file for printing...
1752  */
1753
1754 static int                              /* O - Exit status of filter */
1755 run_pictwps_filter(char       **argv,   /* I - Command-line arguments */
1756                    const char *filename)/* I - Filename */
1757 {
1758   struct stat   fileinfo;               /* Print file information */
1759   const char    *ppdfile;               /* PPD file for destination printer */
1760   int           pid;                    /* Child process ID */
1761   int           fd;                     /* Temporary file descriptor */
1762   int           status;                 /* Exit status of filter */
1763   const char    *printer;               /* PRINTER env var */
1764   static char   ppdenv[1024];           /* PPD environment variable */
1765
1766
1767  /*
1768   * First get the PPD file for the printer...
1769   */
1770
1771   printer = getenv("PRINTER");
1772   if (!printer)
1773   {
1774     _cupsLangPuts(stderr,
1775                   _("ERROR: PRINTER environment variable not defined!\n"));
1776     return (-1);
1777   }
1778
1779   if ((ppdfile = cupsGetPPD(printer)) == NULL)
1780   {
1781     _cupsLangPrintf(stderr,
1782                     _("ERROR: Unable to get PPD file for printer \"%s\" - "
1783                       "%s.\n"), printer, cupsLastErrorString());
1784   }
1785   else
1786   {
1787     snprintf(ppdenv, sizeof(ppdenv), "PPD=%s", ppdfile);
1788     putenv(ppdenv);
1789   }
1790
1791  /*
1792   * Then create a temporary file for printing...
1793   */
1794
1795   if ((fd = cupsTempFd(pstmpname, sizeof(pstmpname))) < 0)
1796   {
1797     _cupsLangPrintError("ERROR", _("Unable to create temporary file"));
1798     if (ppdfile)
1799       unlink(ppdfile);
1800     return (-1);
1801   }
1802
1803  /*
1804   * Get the owner of the spool file - it is owned by the user we want to run
1805   * as...
1806   */
1807
1808   if (argv[6])
1809     stat(argv[6], &fileinfo);
1810   else
1811   {
1812    /*
1813     * Use the OSX defaults, as an up-stream filter created the PICT
1814     * file...
1815     */
1816
1817     fileinfo.st_uid = 1;
1818     fileinfo.st_gid = 80;
1819   }
1820
1821   if (ppdfile)
1822     chown(ppdfile, fileinfo.st_uid, fileinfo.st_gid);
1823
1824   fchown(fd, fileinfo.st_uid, fileinfo.st_gid);
1825
1826  /*
1827   * Finally, run the filter to convert the file...
1828   */
1829
1830   if ((pid = fork()) == 0)
1831   {
1832    /*
1833     * Child process for pictwpstops...  Redirect output of pictwpstops to a
1834     * file...
1835     */
1836
1837     dup2(fd, 1);
1838     close(fd);
1839
1840     if (!getuid())
1841     {
1842      /*
1843       * Change to an unpriviledged user...
1844       */
1845
1846       if (setgid(fileinfo.st_gid))
1847         return (errno);
1848
1849       if (setuid(fileinfo.st_uid))
1850         return (errno);
1851     }
1852
1853     execlp("pictwpstops", printer, argv[1], argv[2], argv[3], argv[4], argv[5],
1854            filename, NULL);
1855     _cupsLangPrintf(stderr, _("ERROR: Unable to exec pictwpstops: %s\n"),
1856                     strerror(errno));
1857     return (errno);
1858   }
1859
1860   close(fd);
1861
1862   if (pid < 0)
1863   {
1864    /*
1865     * Error!
1866     */
1867
1868     _cupsLangPrintf(stderr, _("ERROR: Unable to fork pictwpstops: %s\n"),
1869                     strerror(errno));
1870     if (ppdfile)
1871       unlink(ppdfile);
1872     return (-1);
1873   }
1874
1875  /*
1876   * Now wait for the filter to complete...
1877   */
1878
1879   if (wait(&status) < 0)
1880   {
1881     _cupsLangPrintf(stderr, _("ERROR: Unable to wait for pictwpstops: %s\n"),
1882                     strerror(errno));
1883     close(fd);
1884     if (ppdfile)
1885       unlink(ppdfile);
1886     return (-1);
1887   }
1888
1889   if (ppdfile)
1890     unlink(ppdfile);
1891
1892   close(fd);
1893
1894   if (status)
1895   {
1896     if (status >= 256)
1897       _cupsLangPrintf(stderr, _("ERROR: pictwpstops exited with status %d!\n"),
1898                       status / 256);
1899     else
1900       _cupsLangPrintf(stderr, _("ERROR: pictwpstops exited on signal %d!\n"),
1901                       status);
1902
1903     return (status);
1904   }
1905
1906  /*
1907   * Return with no errors..
1908   */
1909
1910   return (0);
1911 }
1912 #endif /* __APPLE__ */
1913
1914
1915 /*
1916  * 'sigterm_handler()' - Handle 'terminate' signals that stop the backend.
1917  */
1918
1919 static void
1920 sigterm_handler(int sig)                /* I - Signal */
1921 {
1922   (void)sig;    /* remove compiler warnings... */
1923
1924   if (!job_cancelled)
1925   {
1926    /*
1927     * Flag that the job should be cancelled...
1928     */
1929
1930     job_cancelled = 1;
1931     return;
1932   }
1933
1934  /*
1935   * The scheduler already tried to cancel us once, now just terminate
1936   * after removing our temp files!
1937   */
1938
1939   if (tmpfilename[0])
1940     unlink(tmpfilename);
1941
1942 #ifdef __APPLE__
1943   if (pstmpname[0])
1944     unlink(pstmpname);
1945 #endif /* __APPLE__ */
1946
1947   exit(1);
1948 }
1949
1950
1951 /*
1952  * End of "$Id: ipp.c 8950 2010-01-14 22:40:19Z mike $".
1953  */