2 * "$Id: ipp.c 7948 2008-09-17 00:04:12Z mike $"
4 * IPP backend for the Common UNIX Printing System (CUPS).
6 * Copyright 2007-2010 by Apple Inc.
7 * Copyright 1997-2007 by Easy Software Products, all rights reserved.
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/".
15 * This file is subject to the Apple OS-Developed Software exception.
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
29 * sigterm_handler() - Handle 'terminate' signals that stop the backend.
33 * Include necessary headers.
36 #include <cups/http-private.h>
37 #include "backend-private.h"
38 #include <sys/types.h>
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 */
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? */
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
);
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
);
74 static int run_pictwps_filter(char **argv
, const char *filename
);
75 #endif /* __APPLE__ */
76 static void sigterm_handler(int sig
);
80 * 'main()' - Send a file to the printer or server.
84 * printer-uri job-id user title copies options [file]
87 int /* O - Exit status */
88 main(int argc
, /* I - Number of command-line args */
89 char *argv
[]) /* I - Command-line arguments */
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 contimeout
; /* Connection timeout */
121 int delay
; /* Delay for retries... */
122 int compression
, /* Do compression of the job data? */
123 waitjob
, /* Wait for job complete? */
124 waitprinter
; /* Wait for printer ready? */
125 ipp_attribute_t
*job_id_attr
; /* job-id attribute */
126 int job_id
; /* job-id value */
127 ipp_attribute_t
*job_sheets
; /* job-media-sheets-completed */
128 ipp_attribute_t
*job_state
; /* job-state */
129 ipp_attribute_t
*copies_sup
; /* copies-supported */
130 ipp_attribute_t
*format_sup
; /* document-format-supported */
131 ipp_attribute_t
*printer_state
; /* printer-state attribute */
132 ipp_attribute_t
*printer_accepting
; /* printer-is-accepting-jobs */
133 int copies
, /* Number of copies for job */
134 copies_remaining
; /* Number of copies remaining */
135 const char *content_type
, /* CONTENT_TYPE environment variable */
136 *final_content_type
; /* FINAL_CONTENT_TYPE environment var */
137 #if defined(HAVE_SIGACTION) && !defined(HAVE_SIGSET)
138 struct sigaction action
; /* Actions for POSIX signals */
139 #endif /* HAVE_SIGACTION && !HAVE_SIGSET */
140 int version
; /* IPP version */
141 static const char * const pattrs
[] =
142 { /* Printer attributes we want */
144 "document-format-supported",
146 "marker-high-levels",
152 "printer-is-accepting-jobs",
154 "printer-state-message",
155 "printer-state-reasons",
157 static const char * const jattrs
[] =
158 { /* Job attributes we want */
159 "job-media-sheets-completed",
165 * Make sure status messages are not buffered...
168 setbuf(stderr
, NULL
);
171 * Ignore SIGPIPE and catch SIGTERM signals...
175 sigset(SIGPIPE
, SIG_IGN
);
176 sigset(SIGTERM
, sigterm_handler
);
177 #elif defined(HAVE_SIGACTION)
178 memset(&action
, 0, sizeof(action
));
179 action
.sa_handler
= SIG_IGN
;
180 sigaction(SIGPIPE
, &action
, NULL
);
182 sigemptyset(&action
.sa_mask
);
183 sigaddset(&action
.sa_mask
, SIGTERM
);
184 action
.sa_handler
= sigterm_handler
;
185 sigaction(SIGTERM
, &action
, NULL
);
187 signal(SIGPIPE
, SIG_IGN
);
188 signal(SIGTERM
, sigterm_handler
);
189 #endif /* HAVE_SIGSET */
192 * Check command-line...
199 if ((s
= strrchr(argv
[0], '/')) != NULL
)
204 printf("network %s \"Unknown\" \"%s (%s)\"\n",
205 s
, _cupsLangString(cupsLangDefault(),
206 _("Internet Printing Protocol")), s
);
207 return (CUPS_BACKEND_OK
);
211 _cupsLangPrintf(stderr
,
212 _("Usage: %s job-id user title copies options [file]\n"),
214 return (CUPS_BACKEND_STOP
);
218 * Get the (final) content type...
221 if ((content_type
= getenv("CONTENT_TYPE")) == NULL
)
222 content_type
= "application/octet-stream";
224 if ((final_content_type
= getenv("FINAL_CONTENT_TYPE")) == NULL
)
226 final_content_type
= content_type
;
228 if (!strncmp(final_content_type
, "printer/", 8))
229 final_content_type
= "application/vnd.cups-raw";
233 * Extract the hostname and printer name from the URI...
236 if ((device_uri
= cupsBackendDeviceURI(argv
)) == NULL
)
237 return (CUPS_BACKEND_FAILED
);
239 httpSeparateURI(HTTP_URI_CODING_ALL
, device_uri
, scheme
, sizeof(scheme
),
240 username
, sizeof(username
), hostname
, sizeof(hostname
), &port
,
241 resource
, sizeof(resource
));
244 port
= IPP_PORT
; /* Default to port 631 */
246 if (!strcmp(scheme
, "https"))
247 cupsSetEncryption(HTTP_ENCRYPT_ALWAYS
);
249 cupsSetEncryption(HTTP_ENCRYPT_IF_REQUESTED
);
252 * See if there are any options...
259 contimeout
= 7 * 24 * 60 * 60;
261 if ((optptr
= strchr(resource
, '?')) != NULL
)
264 * Yup, terminate the device name string and move to the first
265 * character of the optptr...
271 * Then parse the optptr...
282 while (*optptr
&& *optptr
!= '=' && *optptr
!= '+' && *optptr
!= '&')
285 if ((sep
= *optptr
) != '\0')
296 while (*optptr
&& *optptr
!= '+' && *optptr
!= '&')
306 * Process the option...
309 if (!strcasecmp(name
, "waitjob"))
312 * Wait for job completion?
315 waitjob
= !strcasecmp(value
, "on") ||
316 !strcasecmp(value
, "yes") ||
317 !strcasecmp(value
, "true");
319 else if (!strcasecmp(name
, "waitprinter"))
322 * Wait for printer idle?
325 waitprinter
= !strcasecmp(value
, "on") ||
326 !strcasecmp(value
, "yes") ||
327 !strcasecmp(value
, "true");
329 else if (!strcasecmp(name
, "encryption"))
332 * Enable/disable encryption?
335 if (!strcasecmp(value
, "always"))
336 cupsSetEncryption(HTTP_ENCRYPT_ALWAYS
);
337 else if (!strcasecmp(value
, "required"))
338 cupsSetEncryption(HTTP_ENCRYPT_REQUIRED
);
339 else if (!strcasecmp(value
, "never"))
340 cupsSetEncryption(HTTP_ENCRYPT_NEVER
);
341 else if (!strcasecmp(value
, "ifrequested"))
342 cupsSetEncryption(HTTP_ENCRYPT_IF_REQUESTED
);
345 _cupsLangPrintf(stderr
,
346 _("ERROR: Unknown encryption option value \"%s\"\n"),
350 else if (!strcasecmp(name
, "version"))
352 if (!strcmp(value
, "1.0"))
354 else if (!strcmp(value
, "1.1"))
356 else if (!strcmp(value
, "2.0"))
358 else if (!strcmp(value
, "2.1"))
362 _cupsLangPrintf(stderr
,
363 _("ERROR: Unknown version option value \"%s\"\n"),
368 else if (!strcasecmp(name
, "compression"))
370 compression
= !strcasecmp(value
, "true") ||
371 !strcasecmp(value
, "yes") ||
372 !strcasecmp(value
, "on") ||
373 !strcasecmp(value
, "gzip");
375 #endif /* HAVE_LIBZ */
376 else if (!strcasecmp(name
, "contimeout"))
379 * Set the connection timeout...
383 contimeout
= atoi(value
);
391 _cupsLangPrintf(stderr
,
392 _("ERROR: Unknown option \"%s\" with value \"%s\"\n"),
399 * If we have 7 arguments, print the file named on the command-line.
400 * Otherwise, copy stdin to a temporary file and print the temporary
407 * Copy stdin to a temporary file...
410 int fd
; /* File descriptor */
411 http_addrlist_t
*addrlist
; /* Address list */
412 off_t tbytes
; /* Total bytes copied */
415 fputs("STATE: +connecting-to-device\n", stderr
);
416 fprintf(stderr
, "DEBUG: Looking up \"%s\"...\n", hostname
);
418 if ((addrlist
= httpAddrGetList(hostname
, AF_UNSPEC
, "1")) == NULL
)
420 _cupsLangPrintf(stderr
, _("ERROR: Unable to locate printer \'%s\'\n"),
422 return (CUPS_BACKEND_STOP
);
425 snmp_fd
= _cupsSNMPOpen(addrlist
->addr
.addr
.sa_family
);
427 if ((fd
= cupsTempFd(tmpfilename
, sizeof(tmpfilename
))) < 0)
429 _cupsLangPrintError(_("ERROR: Unable to create temporary file"));
430 return (CUPS_BACKEND_FAILED
);
433 _cupsLangPuts(stderr
, _("INFO: Copying print data...\n"));
435 tbytes
= backendRunLoop(-1, fd
, snmp_fd
, &(addrlist
->addr
), 0, 0,
436 backendNetworkSideCB
);
439 _cupsSNMPClose(snmp_fd
);
441 httpAddrFreeList(addrlist
);
446 * Don't try printing files less than 2 bytes...
451 _cupsLangPuts(stderr
, _("ERROR: Empty print file\n"));
453 return (CUPS_BACKEND_FAILED
);
457 * Point to the single file from stdin...
460 filename
= tmpfilename
;
468 * Point to the files on the command-line...
471 num_files
= argc
- 6;
477 compress_files(num_files
, files
);
478 #endif /* HAVE_LIBZ */
481 fprintf(stderr
, "DEBUG: %d files to send in job...\n", num_files
);
484 * Set the authentication info, if any...
487 cupsSetPasswordCB(password_cb
);
492 * Use authenticaion information in the device URI...
495 if ((password
= strchr(username
, ':')) != NULL
)
498 cupsSetUser(username
);
503 * Try loading authentication information from the environment.
506 const char *ptr
= getenv("AUTH_USERNAME");
511 password
= getenv("AUTH_PASSWORD");
515 * Try connecting to the remote server...
519 start_time
= time(NULL
);
521 fputs("STATE: +connecting-to-device\n", stderr
);
525 fprintf(stderr
, "DEBUG: Connecting to %s:%d\n", hostname
, port
);
526 _cupsLangPuts(stderr
, _("INFO: Connecting to printer...\n"));
528 if ((http
= httpConnectEncrypt(hostname
, port
, cupsEncryption())) == NULL
)
533 if (getenv("CLASS") != NULL
)
536 * If the CLASS environment variable is set, the job was submitted
537 * to a class and not to a specific queue. In this case, we want
538 * to abort immediately so that the job can be requeued on the next
539 * available printer in the class.
542 _cupsLangPuts(stderr
,
543 _("INFO: Unable to contact printer, queuing on next "
544 "printer in class...\n"));
550 * Sleep 5 seconds to keep the job from requeuing too rapidly...
555 return (CUPS_BACKEND_FAILED
);
558 if (errno
== ECONNREFUSED
|| errno
== EHOSTDOWN
||
559 errno
== EHOSTUNREACH
)
561 if (contimeout
&& (time(NULL
) - start_time
) > contimeout
)
563 _cupsLangPuts(stderr
, _("ERROR: Printer not responding\n"));
564 return (CUPS_BACKEND_FAILED
);
567 _cupsLangPrintf(stderr
,
568 _("WARNING: Network host \'%s\' is busy; "
569 "will retry in %d seconds...\n"),
579 _cupsLangPrintf(stderr
, _("ERROR: Unable to locate printer \'%s\'\n"),
581 return (CUPS_BACKEND_STOP
);
585 fprintf(stderr
, "DEBUG: Connection error: %s\n", strerror(errno
));
586 _cupsLangPuts(stderr
,
587 _("ERROR: Unable to connect to printer; will retry in 30 "
596 while (http
== NULL
);
598 if (job_cancelled
|| !http
)
603 return (CUPS_BACKEND_FAILED
);
606 fputs("STATE: -connecting-to-device\n", stderr
);
607 _cupsLangPuts(stderr
, _("INFO: Connected to printer...\n"));
610 if (http
->hostaddr
->addr
.sa_family
== AF_INET6
)
611 fprintf(stderr
, "DEBUG: Connected to [%s]:%d (IPv6)...\n",
612 httpAddrString(http
->hostaddr
, addrname
, sizeof(addrname
)),
613 ntohs(http
->hostaddr
->ipv6
.sin6_port
));
615 #endif /* AF_INET6 */
616 if (http
->hostaddr
->addr
.sa_family
== AF_INET
)
617 fprintf(stderr
, "DEBUG: Connected to %s:%d (IPv4)...\n",
618 httpAddrString(http
->hostaddr
, addrname
, sizeof(addrname
)),
619 ntohs(http
->hostaddr
->ipv4
.sin_port
));
622 * See if the printer supports SNMP...
625 if ((snmp_fd
= _cupsSNMPOpen(http
->hostaddr
->addr
.sa_family
)) >= 0)
626 have_supplies
= !backendSNMPSupplies(snmp_fd
, http
->hostaddr
, &start_count
,
629 have_supplies
= start_count
= 0;
632 * Build a URI for the printer and fill the standard IPP attributes for
633 * an IPP_PRINT_FILE request. We can't use the URI in argv[0] because it
634 * might contain username:password information...
637 httpAssembleURI(HTTP_URI_CODING_ALL
, uri
, sizeof(uri
), scheme
, NULL
, hostname
,
641 * First validate the destination and see if the device supports multiple
642 * copies. We have to do this because some IPP servers (e.g. HP JetDirect)
643 * don't support the copies attribute...
653 * Check for side-channel requests...
656 backendCheckSideChannel(snmp_fd
, http
->hostaddr
);
659 * Build the IPP request...
662 request
= ippNewRequest(IPP_GET_PRINTER_ATTRIBUTES
);
663 request
->request
.op
.version
[0] = version
/ 10;
664 request
->request
.op
.version
[1] = version
% 10;
666 ippAddString(request
, IPP_TAG_OPERATION
, IPP_TAG_URI
, "printer-uri",
669 ippAddStrings(request
, IPP_TAG_OPERATION
, IPP_TAG_KEYWORD
,
670 "requested-attributes", sizeof(pattrs
) / sizeof(pattrs
[0]),
677 fputs("DEBUG: Getting supported attributes...\n", stderr
);
679 if (http
->version
< HTTP_1_1
)
682 if ((supported
= cupsDoRequest(http
, request
, resource
)) == NULL
)
683 ipp_status
= cupsLastError();
685 ipp_status
= supported
->request
.status
.status_code
;
687 if (ipp_status
> IPP_OK_CONFLICT
)
689 if (ipp_status
== IPP_PRINTER_BUSY
||
690 ipp_status
== IPP_SERVICE_UNAVAILABLE
)
692 if (contimeout
&& (time(NULL
) - start_time
) > contimeout
)
694 _cupsLangPuts(stderr
, _("ERROR: Printer not responding\n"));
695 return (CUPS_BACKEND_FAILED
);
698 _cupsLangPrintf(stderr
,
699 _("WARNING: Network host \'%s\' is busy; will retry in "
700 "%d seconds...\n"), hostname
, delay
);
702 report_printer_state(supported
, 0);
709 else if ((ipp_status
== IPP_BAD_REQUEST
||
710 ipp_status
== IPP_VERSION_NOT_SUPPORTED
) && version
> 10)
713 * Switch to IPP/1.0...
716 _cupsLangPrintf(stderr
,
717 _("INFO: Printer does not support IPP/%d.%d, trying "
718 "IPP/1.0...\n"), version
/ 10, version
% 10);
722 else if (ipp_status
== IPP_NOT_FOUND
)
724 _cupsLangPuts(stderr
, _("ERROR: Destination printer does not exist\n"));
727 ippDelete(supported
);
729 return (CUPS_BACKEND_STOP
);
731 else if (ipp_status
== IPP_NOT_AUTHORIZED
|| ipp_status
== IPP_FORBIDDEN
)
733 if (!strncmp(httpGetField(http
, HTTP_FIELD_WWW_AUTHENTICATE
),
735 auth_info_required
= "negotiate";
737 fprintf(stderr
, "ATTR: auth-info-required=%s\n", auth_info_required
);
738 return (CUPS_BACKEND_AUTH_REQUIRED
);
742 _cupsLangPrintf(stderr
,
743 _("ERROR: Unable to get printer status (%s)\n"),
744 cupsLastErrorString());
749 ippDelete(supported
);
753 else if ((copies_sup
= ippFindAttribute(supported
, "copies-supported",
754 IPP_TAG_RANGE
)) != NULL
)
757 * Has the "copies-supported" attribute - does it have an upper
761 if (copies_sup
->values
[0].range
.upper
<= 1)
762 copies_sup
= NULL
; /* No */
765 format_sup
= ippFindAttribute(supported
, "document-format-supported",
770 fprintf(stderr
, "DEBUG: document-format-supported (%d values)\n",
771 format_sup
->num_values
);
772 for (i
= 0; i
< format_sup
->num_values
; i
++)
773 fprintf(stderr
, "DEBUG: [%d] = \"%s\"\n", i
,
774 format_sup
->values
[i
].string
.text
);
777 report_printer_state(supported
, 0);
779 while (ipp_status
> IPP_OK_CONFLICT
);
782 * See if the printer is accepting jobs and is not stopped; if either
783 * condition is true and we are printing to a class, requeue the job...
786 if (getenv("CLASS") != NULL
)
788 printer_state
= ippFindAttribute(supported
, "printer-state",
790 printer_accepting
= ippFindAttribute(supported
, "printer-is-accepting-jobs",
793 if (printer_state
== NULL
||
794 (printer_state
->values
[0].integer
> IPP_PRINTER_PROCESSING
&&
796 printer_accepting
== NULL
||
797 !printer_accepting
->values
[0].boolean
)
800 * If the CLASS environment variable is set, the job was submitted
801 * to a class and not to a specific queue. In this case, we want
802 * to abort immediately so that the job can be requeued on the next
803 * available printer in the class.
806 _cupsLangPuts(stderr
,
807 _("INFO: Unable to contact printer, queuing on next "
808 "printer in class...\n"));
810 ippDelete(supported
);
817 * Sleep 5 seconds to keep the job from requeuing too rapidly...
822 return (CUPS_BACKEND_FAILED
);
827 * See if the printer supports multiple copies...
830 copies
= atoi(argv
[4]);
832 if (copies_sup
|| argc
< 7)
834 copies_remaining
= 1;
840 copies_remaining
= copies
;
843 * Then issue the print-job request...
848 while (copies_remaining
> 0)
851 * Check for side-channel requests...
854 backendCheckSideChannel(snmp_fd
, http
->hostaddr
);
857 * Build the IPP request...
864 request
= ippNewRequest(IPP_CREATE_JOB
);
866 request
= ippNewRequest(IPP_PRINT_JOB
);
868 request
->request
.op
.version
[0] = version
/ 10;
869 request
->request
.op
.version
[1] = version
% 10;
871 ippAddString(request
, IPP_TAG_OPERATION
, IPP_TAG_URI
, "printer-uri",
874 fprintf(stderr
, "DEBUG: printer-uri = \"%s\"\n", uri
);
877 ippAddString(request
, IPP_TAG_OPERATION
, IPP_TAG_NAME
,
878 "requesting-user-name", NULL
, argv
[2]);
880 fprintf(stderr
, "DEBUG: requesting-user-name = \"%s\"\n", argv
[2]);
883 * Only add a "job-name" attribute if the remote server supports
884 * copy generation - some IPP implementations like HP's don't seem
885 * to like UTF-8 job names (STR #1837)...
888 if (argv
[3][0] && copies_sup
)
889 ippAddString(request
, IPP_TAG_OPERATION
, IPP_TAG_NAME
, "job-name", NULL
,
892 fprintf(stderr
, "DEBUG: job-name = \"%s\"\n", argv
[3]);
896 ippAddString(request
, IPP_TAG_OPERATION
, IPP_TAG_KEYWORD
,
897 "compression", NULL
, "gzip");
898 #endif /* HAVE_LIBZ */
901 * Handle options on the command-line...
905 num_options
= cupsParseOptions(argv
[5], 0, &options
);
908 if (!strcasecmp(final_content_type
, "application/pictwps") &&
911 if (format_sup
!= NULL
)
913 for (i
= 0; i
< format_sup
->num_values
; i
++)
914 if (!strcasecmp(final_content_type
, format_sup
->values
[i
].string
.text
))
918 if (format_sup
== NULL
|| i
>= format_sup
->num_values
)
921 * Remote doesn't support "application/pictwps" (i.e. it's not MacOS X)
922 * so convert the document to PostScript...
925 if (run_pictwps_filter(argv
, files
[0]))
933 return (CUPS_BACKEND_FAILED
);
936 files
[0] = pstmpname
;
939 * Change the MIME type to application/postscript and change the
940 * number of copies to 1...
943 final_content_type
= "application/postscript";
945 copies_remaining
= 1;
949 #endif /* __APPLE__ */
951 if (format_sup
!= NULL
)
953 for (i
= 0; i
< format_sup
->num_values
; i
++)
954 if (!strcasecmp(final_content_type
, format_sup
->values
[i
].string
.text
))
957 if (i
< format_sup
->num_values
)
958 ippAddString(request
, IPP_TAG_OPERATION
, IPP_TAG_MIMETYPE
,
959 "document-format", NULL
, final_content_type
);
962 if (copies_sup
&& version
> 10 && send_options
)
965 * Only send options if the destination printer supports the copies
966 * attribute and IPP/1.1. This is a hack for the HP and Lexmark
967 * implementations of IPP, which do not accept extension attributes
968 * and incorrectly report a client-error-bad-request error instead of
969 * the successful-ok-unsupported-attributes status. In short, at least
970 * some HP and Lexmark implementations of IPP are non-compliant.
973 cupsEncodeOptions(request
, num_options
, options
);
975 ippAddInteger(request
, IPP_TAG_JOB
, IPP_TAG_INTEGER
, "copies",
979 cupsFreeOptions(num_options
, options
);
982 * If copies aren't supported, then we are likely dealing with an HP
983 * JetDirect. The HP IPP implementation seems to close the connection
984 * after every request - that is, it does *not* implement HTTP Keep-
985 * Alive, which is REQUIRED by HTTP/1.1...
995 if (http
->version
< HTTP_1_1
)
999 response
= cupsDoRequest(http
, request
, resource
);
1001 response
= cupsDoFileRequest(http
, request
, resource
, files
[0]);
1003 ipp_status
= cupsLastError();
1005 if (ipp_status
> IPP_OK_CONFLICT
)
1012 if (ipp_status
== IPP_SERVICE_UNAVAILABLE
||
1013 ipp_status
== IPP_PRINTER_BUSY
)
1015 _cupsLangPuts(stderr
,
1016 _("INFO: Printer busy; will retry in 10 seconds...\n"));
1019 else if ((ipp_status
== IPP_BAD_REQUEST
||
1020 ipp_status
== IPP_VERSION_NOT_SUPPORTED
) && version
> 10)
1023 * Switch to IPP/1.0...
1026 _cupsLangPrintf(stderr
,
1027 _("INFO: Printer does not support IPP/%d.%d, trying "
1028 "IPP/1.0...\n"), version
/ 10, version
% 10);
1030 httpReconnect(http
);
1035 * Update auth-info-required as needed...
1038 _cupsLangPrintf(stderr
, _("ERROR: Print file was not accepted (%s)\n"),
1039 cupsLastErrorString());
1041 if (ipp_status
== IPP_NOT_AUTHORIZED
|| ipp_status
== IPP_FORBIDDEN
)
1043 fprintf(stderr
, "DEBUG: WWW-Authenticate=\"%s\"\n",
1044 httpGetField(http
, HTTP_FIELD_WWW_AUTHENTICATE
));
1047 * Normal authentication goes through the password callback, which sets
1048 * auth_info_required to "username,password". Kerberos goes directly
1049 * through GSSAPI, so look for Negotiate in the WWW-Authenticate header
1050 * here and set auth_info_required as needed...
1053 if (!strncmp(httpGetField(http
, HTTP_FIELD_WWW_AUTHENTICATE
),
1055 auth_info_required
= "negotiate";
1059 else if ((job_id_attr
= ippFindAttribute(response
, "job-id",
1060 IPP_TAG_INTEGER
)) == NULL
)
1062 _cupsLangPuts(stderr
,
1063 _("NOTICE: Print file accepted - job ID unknown.\n"));
1068 job_id
= job_id_attr
->values
[0].integer
;
1069 _cupsLangPrintf(stderr
, _("NOTICE: Print file accepted - job ID %d.\n"),
1073 ippDelete(response
);
1078 if (job_id
&& num_files
> 1)
1080 for (i
= 0; i
< num_files
; i
++)
1083 * Check for side-channel requests...
1086 backendCheckSideChannel(snmp_fd
, http
->hostaddr
);
1089 * Send the next file in the job...
1092 request
= ippNewRequest(IPP_SEND_DOCUMENT
);
1093 request
->request
.op
.version
[0] = version
/ 10;
1094 request
->request
.op
.version
[1] = version
% 10;
1096 ippAddString(request
, IPP_TAG_OPERATION
, IPP_TAG_URI
, "printer-uri",
1099 ippAddInteger(request
, IPP_TAG_OPERATION
, IPP_TAG_INTEGER
, "job-id",
1103 ippAddString(request
, IPP_TAG_OPERATION
, IPP_TAG_NAME
,
1104 "requesting-user-name", NULL
, argv
[2]);
1106 if ((i
+ 1) == num_files
)
1107 ippAddBoolean(request
, IPP_TAG_OPERATION
, "last-document", 1);
1109 ippAddString(request
, IPP_TAG_OPERATION
, IPP_TAG_MIMETYPE
,
1110 "document-format", NULL
, content_type
);
1112 if (http
->version
< HTTP_1_1
)
1113 httpReconnect(http
);
1115 ippDelete(cupsDoFileRequest(http
, request
, resource
, files
[i
]));
1117 if (cupsLastError() > IPP_OK_CONFLICT
)
1119 ipp_status
= cupsLastError();
1121 _cupsLangPrintf(stderr
,
1122 _("ERROR: Unable to add file %d to job: %s\n"),
1123 job_id
, cupsLastErrorString());
1129 if (ipp_status
<= IPP_OK_CONFLICT
&& argc
> 6)
1131 fprintf(stderr
, "PAGE: 1 %d\n", copies_sup
? atoi(argv
[4]) : 1);
1132 copies_remaining
--;
1134 else if (ipp_status
== IPP_SERVICE_UNAVAILABLE
||
1135 ipp_status
== IPP_PRINTER_BUSY
)
1138 copies_remaining
--;
1141 * Wait for the job to complete...
1144 if (!job_id
|| !waitjob
)
1147 _cupsLangPuts(stderr
, _("INFO: Waiting for job to complete...\n"));
1149 for (delay
= 1; !job_cancelled
;)
1152 * Check for side-channel requests...
1155 backendCheckSideChannel(snmp_fd
, http
->hostaddr
);
1158 * Build an IPP_GET_JOB_ATTRIBUTES request...
1161 request
= ippNewRequest(IPP_GET_JOB_ATTRIBUTES
);
1162 request
->request
.op
.version
[0] = version
/ 10;
1163 request
->request
.op
.version
[1] = version
% 10;
1165 ippAddString(request
, IPP_TAG_OPERATION
, IPP_TAG_URI
, "printer-uri",
1168 ippAddInteger(request
, IPP_TAG_OPERATION
, IPP_TAG_INTEGER
, "job-id",
1172 ippAddString(request
, IPP_TAG_OPERATION
, IPP_TAG_NAME
,
1173 "requesting-user-name", NULL
, argv
[2]);
1175 ippAddStrings(request
, IPP_TAG_OPERATION
, IPP_TAG_KEYWORD
,
1176 "requested-attributes", sizeof(jattrs
) / sizeof(jattrs
[0]),
1183 if (!copies_sup
|| http
->version
< HTTP_1_1
)
1184 httpReconnect(http
);
1186 response
= cupsDoRequest(http
, request
, resource
);
1187 ipp_status
= cupsLastError();
1189 if (ipp_status
== IPP_NOT_FOUND
)
1192 * Job has gone away and/or the server has no job history...
1195 ippDelete(response
);
1197 ipp_status
= IPP_OK
;
1201 if (ipp_status
> IPP_OK_CONFLICT
)
1203 if (ipp_status
!= IPP_SERVICE_UNAVAILABLE
&&
1204 ipp_status
!= IPP_PRINTER_BUSY
)
1206 ippDelete(response
);
1208 _cupsLangPrintf(stderr
,
1209 _("ERROR: Unable to get job %d attributes (%s)\n"),
1210 job_id
, cupsLastErrorString());
1217 if ((job_state
= ippFindAttribute(response
, "job-state",
1218 IPP_TAG_ENUM
)) != NULL
)
1221 * Stop polling if the job is finished or pending-held...
1224 if (job_state
->values
[0].integer
> IPP_JOB_STOPPED
)
1226 if ((job_sheets
= ippFindAttribute(response
,
1227 "job-media-sheets-completed",
1228 IPP_TAG_INTEGER
)) != NULL
)
1229 fprintf(stderr
, "PAGE: total %d\n",
1230 job_sheets
->values
[0].integer
);
1232 ippDelete(response
);
1239 * If the printer does not return a job-state attribute, it does not
1240 * conform to the IPP specification - break out immediately and fail
1244 fputs("DEBUG: No job-state available from printer - stopping queue.\n",
1246 ipp_status
= IPP_INTERNAL_ERROR
;
1251 ippDelete(response
);
1254 * Check the printer state and report it if necessary...
1257 check_printer_state(http
, uri
, resource
, argv
[2], version
, job_id
);
1260 * Wait 1-10 seconds before polling again...
1272 * Cancel the job as needed...
1275 if (job_cancelled
&& job_id
)
1276 cancel_job(http
, uri
, job_id
, resource
, argv
[2], version
);
1279 * Check the printer state and report it if necessary...
1282 check_printer_state(http
, uri
, resource
, argv
[2], version
, job_id
);
1285 * Collect the final page count as needed...
1288 if (have_supplies
&&
1289 !backendSNMPSupplies(snmp_fd
, http
->hostaddr
, &page_count
, NULL
) &&
1290 page_count
> start_count
)
1291 fprintf(stderr
, "PAGE: total %d\n", page_count
- start_count
);
1295 * See if we used Kerberos at all...
1299 auth_info_required
= "negotiate";
1300 #endif /* HAVE_GSSAPI */
1308 ippDelete(supported
);
1311 * Remove the temporary file(s) if necessary...
1315 unlink(tmpfilename
);
1320 for (i
= 0; i
< num_files
; i
++)
1323 #endif /* HAVE_LIBZ */
1328 #endif /* __APPLE__ */
1331 * Return the queue status...
1334 fprintf(stderr
, "ATTR: auth-info-required=%s\n", auth_info_required
);
1336 if (ipp_status
== IPP_NOT_AUTHORIZED
|| ipp_status
== IPP_FORBIDDEN
)
1337 return (CUPS_BACKEND_AUTH_REQUIRED
);
1338 else if (ipp_status
== IPP_INTERNAL_ERROR
)
1339 return (CUPS_BACKEND_STOP
);
1340 else if (ipp_status
> IPP_OK_CONFLICT
)
1341 return (CUPS_BACKEND_FAILED
);
1344 _cupsLangPuts(stderr
, _("INFO: Ready to print.\n"));
1345 return (CUPS_BACKEND_OK
);
1351 * 'cancel_job()' - Cancel a print job.
1355 cancel_job(http_t
*http
, /* I - HTTP connection */
1356 const char *uri
, /* I - printer-uri */
1357 int id
, /* I - job-id */
1358 const char *resource
, /* I - Resource path */
1359 const char *user
, /* I - requesting-user-name */
1360 int version
) /* I - IPP version */
1362 ipp_t
*request
; /* Cancel-Job request */
1365 _cupsLangPuts(stderr
, _("INFO: Canceling print job...\n"));
1367 request
= ippNewRequest(IPP_CANCEL_JOB
);
1368 request
->request
.op
.version
[0] = version
/ 10;
1369 request
->request
.op
.version
[1] = version
% 10;
1371 ippAddString(request
, IPP_TAG_OPERATION
, IPP_TAG_URI
, "printer-uri",
1373 ippAddInteger(request
, IPP_TAG_OPERATION
, IPP_TAG_INTEGER
, "job-id", id
);
1375 if (user
&& user
[0])
1376 ippAddString(request
, IPP_TAG_OPERATION
, IPP_TAG_NAME
,
1377 "requesting-user-name", NULL
, user
);
1383 if (http
->version
< HTTP_1_1
)
1384 httpReconnect(http
);
1386 ippDelete(cupsDoRequest(http
, request
, resource
));
1388 if (cupsLastError() > IPP_OK_CONFLICT
)
1389 _cupsLangPrintf(stderr
, _("ERROR: Unable to cancel job %d: %s\n"), id
,
1390 cupsLastErrorString());
1395 * 'check_printer_state()' - Check the printer state...
1399 check_printer_state(
1400 http_t
*http
, /* I - HTTP connection */
1401 const char *uri
, /* I - Printer URI */
1402 const char *resource
, /* I - Resource path */
1403 const char *user
, /* I - Username, if any */
1404 int version
, /* I - IPP version */
1405 int job_id
) /* I - Current job ID */
1407 ipp_t
*request
, /* IPP request */
1408 *response
; /* IPP response */
1409 static const char * const attrs
[] = /* Attributes we want */
1416 "printer-state-message",
1417 "printer-state-reasons"
1422 * Check on the printer state...
1425 request
= ippNewRequest(IPP_GET_PRINTER_ATTRIBUTES
);
1426 request
->request
.op
.version
[0] = version
/ 10;
1427 request
->request
.op
.version
[1] = version
% 10;
1429 ippAddString(request
, IPP_TAG_OPERATION
, IPP_TAG_URI
, "printer-uri",
1432 if (user
&& user
[0])
1433 ippAddString(request
, IPP_TAG_OPERATION
, IPP_TAG_NAME
,
1434 "requesting-user-name", NULL
, user
);
1436 ippAddStrings(request
, IPP_TAG_OPERATION
, IPP_TAG_KEYWORD
,
1437 "requested-attributes",
1438 (int)(sizeof(attrs
) / sizeof(attrs
[0])), NULL
, attrs
);
1444 if (http
->version
< HTTP_1_1
)
1445 httpReconnect(http
);
1447 if ((response
= cupsDoRequest(http
, request
, resource
)) != NULL
)
1449 report_printer_state(response
, job_id
);
1450 ippDelete(response
);
1457 * 'compress_files()' - Compress print files...
1461 compress_files(int num_files
, /* I - Number of files */
1462 char **files
) /* I - Files */
1464 int i
, /* Looping var */
1465 fd
; /* Temporary file descriptor */
1466 ssize_t bytes
; /* Bytes read/written */
1467 size_t total
; /* Total bytes read */
1468 cups_file_t
*in
, /* Input file */
1469 *out
; /* Output file */
1470 struct stat outinfo
; /* Output file information */
1471 char filename
[1024], /* Temporary filename */
1472 buffer
[32768]; /* Copy buffer */
1475 fprintf(stderr
, "DEBUG: Compressing %d job files...\n", num_files
);
1476 for (i
= 0; i
< num_files
; i
++)
1478 if ((fd
= cupsTempFd(filename
, sizeof(filename
))) < 0)
1480 _cupsLangPrintf(stderr
,
1481 _("ERROR: Unable to create temporary compressed print "
1482 "file: %s\n"), strerror(errno
));
1483 exit(CUPS_BACKEND_FAILED
);
1486 if ((out
= cupsFileOpenFd(fd
, "w9")) == NULL
)
1488 _cupsLangPrintf(stderr
,
1489 _("ERROR: Unable to open temporary compressed print "
1490 "file: %s\n"), strerror(errno
));
1491 exit(CUPS_BACKEND_FAILED
);
1494 if ((in
= cupsFileOpen(files
[i
], "r")) == NULL
)
1496 _cupsLangPrintf(stderr
,
1497 _("ERROR: Unable to open print file \"%s\": %s\n"),
1498 files
[i
], strerror(errno
));
1500 exit(CUPS_BACKEND_FAILED
);
1504 while ((bytes
= cupsFileRead(in
, buffer
, sizeof(buffer
))) > 0)
1505 if (cupsFileWrite(out
, buffer
, bytes
) < bytes
)
1507 _cupsLangPrintf(stderr
,
1508 _("ERROR: Unable to write %d bytes to \"%s\": %s\n"),
1509 (int)bytes
, filename
, strerror(errno
));
1512 exit(CUPS_BACKEND_FAILED
);
1520 files
[i
] = strdup(filename
);
1522 if (!stat(filename
, &outinfo
))
1524 "DEBUG: File %d compressed to %.1f%% of original size, "
1525 CUPS_LLFMT
" bytes...\n",
1526 i
+ 1, 100.0 * outinfo
.st_size
/ total
,
1527 CUPS_LLCAST outinfo
.st_size
);
1530 #endif /* HAVE_LIBZ */
1534 * 'password_cb()' - Disable the password prompt for cupsDoFileRequest().
1537 static const char * /* O - Password */
1538 password_cb(const char *prompt
) /* I - Prompt (not used) */
1543 * Remember that we need to authenticate...
1546 auth_info_required
= "username,password";
1548 if (password
&& *password
&& password_tries
< 3)
1557 * Give up after 3 tries or if we don't have a password to begin with...
1566 * 'report_attr()' - Report an IPP attribute value.
1570 report_attr(ipp_attribute_t
*attr
) /* I - Attribute */
1572 int i
; /* Looping var */
1573 char value
[1024], /* Value string */
1574 *valptr
, /* Pointer into value string */
1575 *attrptr
; /* Pointer into attribute value */
1579 * Convert the attribute values into quoted strings...
1582 for (i
= 0, valptr
= value
;
1583 i
< attr
->num_values
&& valptr
< (value
+ sizeof(value
) - 10);
1589 switch (attr
->value_tag
)
1591 case IPP_TAG_INTEGER
:
1593 snprintf(valptr
, sizeof(value
) - (valptr
- value
), "%d",
1594 attr
->values
[i
].integer
);
1595 valptr
+= strlen(valptr
);
1600 case IPP_TAG_KEYWORD
:
1602 for (attrptr
= attr
->values
[i
].string
.text
;
1603 *attrptr
&& valptr
< (value
+ sizeof(value
) - 10);
1606 if (*attrptr
== '\\' || *attrptr
== '\"')
1609 *valptr
++ = *attrptr
;
1616 * Unsupported value type...
1626 * Tell the scheduler about the new values...
1629 fprintf(stderr
, "ATTR: %s=%s\n", attr
->name
, value
);
1634 * 'report_printer_state()' - Report the printer state.
1637 static int /* O - Number of reasons shown */
1638 report_printer_state(ipp_t
*ipp
, /* I - IPP response */
1639 int job_id
) /* I - Current job ID */
1641 int i
; /* Looping var */
1642 int count
; /* Count of reasons shown... */
1643 ipp_attribute_t
*psm
, /* printer-state-message */
1644 *reasons
, /* printer-state-reasons */
1645 *marker
; /* marker-* attributes */
1646 const char *reason
; /* Current reason */
1647 const char *prefix
; /* Prefix for STATE: line */
1648 char state
[1024]; /* State string */
1651 if ((psm
= ippFindAttribute(ipp
, "printer-state-message",
1652 IPP_TAG_TEXT
)) != NULL
)
1653 fprintf(stderr
, "INFO: %s\n", psm
->values
[0].string
.text
);
1655 if ((reasons
= ippFindAttribute(ipp
, "printer-state-reasons",
1656 IPP_TAG_KEYWORD
)) == NULL
)
1662 for (i
= 0, count
= 0; i
< reasons
->num_values
; i
++)
1664 reason
= reasons
->values
[i
].string
.text
;
1666 if (strcmp(reason
, "paused") &&
1667 strcmp(reason
, "com.apple.print.recoverable-warning"))
1669 strlcat(state
, prefix
, sizeof(state
));
1670 strlcat(state
, reason
, sizeof(state
));
1677 fprintf(stderr
, "%s\n", state
);
1680 * Relay the current marker-* attribute values...
1683 if ((marker
= ippFindAttribute(ipp
, "marker-colors", IPP_TAG_NAME
)) != NULL
)
1684 report_attr(marker
);
1685 if ((marker
= ippFindAttribute(ipp
, "marker-high-levels",
1686 IPP_TAG_INTEGER
)) != NULL
)
1687 report_attr(marker
);
1688 if ((marker
= ippFindAttribute(ipp
, "marker-levels",
1689 IPP_TAG_INTEGER
)) != NULL
)
1690 report_attr(marker
);
1691 if ((marker
= ippFindAttribute(ipp
, "marker-low-levels",
1692 IPP_TAG_INTEGER
)) != NULL
)
1693 report_attr(marker
);
1694 if ((marker
= ippFindAttribute(ipp
, "marker-message", IPP_TAG_TEXT
)) != NULL
)
1695 report_attr(marker
);
1696 if ((marker
= ippFindAttribute(ipp
, "marker-names", IPP_TAG_NAME
)) != NULL
)
1697 report_attr(marker
);
1698 if ((marker
= ippFindAttribute(ipp
, "marker-types", IPP_TAG_KEYWORD
)) != NULL
)
1699 report_attr(marker
);
1707 * 'run_pictwps_filter()' - Convert PICT files to PostScript when printing
1710 * This step is required because the PICT format is not documented and
1711 * subject to change, so developing a filter for other OS's is infeasible.
1712 * Also, fonts required by the PICT file need to be embedded on the
1713 * client side (which has the fonts), so we run the filter to get a
1714 * PostScript file for printing...
1717 static int /* O - Exit status of filter */
1718 run_pictwps_filter(char **argv
, /* I - Command-line arguments */
1719 const char *filename
)/* I - Filename */
1721 struct stat fileinfo
; /* Print file information */
1722 const char *ppdfile
; /* PPD file for destination printer */
1723 int pid
; /* Child process ID */
1724 int fd
; /* Temporary file descriptor */
1725 int status
; /* Exit status of filter */
1726 const char *printer
; /* PRINTER env var */
1727 static char ppdenv
[1024]; /* PPD environment variable */
1731 * First get the PPD file for the printer...
1734 printer
= getenv("PRINTER");
1737 _cupsLangPuts(stderr
,
1738 _("ERROR: PRINTER environment variable not defined\n"));
1742 if ((ppdfile
= cupsGetPPD(printer
)) == NULL
)
1744 _cupsLangPrintf(stderr
,
1745 _("ERROR: Unable to get PPD file for printer \"%s\" - "
1746 "%s.\n"), printer
, cupsLastErrorString());
1750 snprintf(ppdenv
, sizeof(ppdenv
), "PPD=%s", ppdfile
);
1755 * Then create a temporary file for printing...
1758 if ((fd
= cupsTempFd(pstmpname
, sizeof(pstmpname
))) < 0)
1760 _cupsLangPrintError(_("ERROR: Unable to create temporary file"));
1767 * Get the owner of the spool file - it is owned by the user we want to run
1772 stat(argv
[6], &fileinfo
);
1776 * Use the OSX defaults, as an up-stream filter created the PICT
1780 fileinfo
.st_uid
= 1;
1781 fileinfo
.st_gid
= 80;
1785 chown(ppdfile
, fileinfo
.st_uid
, fileinfo
.st_gid
);
1787 fchown(fd
, fileinfo
.st_uid
, fileinfo
.st_gid
);
1790 * Finally, run the filter to convert the file...
1793 if ((pid
= fork()) == 0)
1796 * Child process for pictwpstops... Redirect output of pictwpstops to a
1806 * Change to an unpriviledged user...
1809 if (setgid(fileinfo
.st_gid
))
1812 if (setuid(fileinfo
.st_uid
))
1816 execlp("pictwpstops", printer
, argv
[1], argv
[2], argv
[3], argv
[4], argv
[5],
1818 _cupsLangPrintf(stderr
, _("ERROR: Unable to exec pictwpstops: %s\n"),
1831 _cupsLangPrintf(stderr
, _("ERROR: Unable to fork pictwpstops: %s\n"),
1839 * Now wait for the filter to complete...
1842 if (wait(&status
) < 0)
1844 _cupsLangPrintf(stderr
, _("ERROR: Unable to wait for pictwpstops: %s\n"),
1860 _cupsLangPrintf(stderr
, _("ERROR: pictwpstops exited with status %d\n"),
1863 _cupsLangPrintf(stderr
, _("ERROR: pictwpstops exited on signal %d\n"),
1870 * Return with no errors..
1875 #endif /* __APPLE__ */
1879 * 'sigterm_handler()' - Handle 'terminate' signals that stop the backend.
1883 sigterm_handler(int sig
) /* I - Signal */
1885 (void)sig
; /* remove compiler warnings... */
1890 * Flag that the job should be cancelled...
1898 * The scheduler already tried to cancel us once, now just terminate
1899 * after removing our temp files!
1903 unlink(tmpfilename
);
1908 #endif /* __APPLE__ */
1915 * End of "$Id: ipp.c 7948 2008-09-17 00:04:12Z mike $".