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-2009 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 */
49 static char pstmpname
[1024] = ""; /* Temporary PostScript file name */
50 #endif /* __APPLE__ */
51 static char tmpfilename
[1024] = ""; /* Temporary spool file name */
52 static int job_cancelled
= 0; /* Job cancelled? */
59 static void cancel_job(http_t
*http
, const char *uri
, int id
,
60 const char *resource
, const char *user
, int version
);
61 static void check_printer_state(http_t
*http
, const char *uri
,
62 const char *resource
, const char *user
,
63 int version
, int job_id
);
65 static void compress_files(int num_files
, char **files
);
66 #endif /* HAVE_LIBZ */
67 static const char *password_cb(const char *);
68 static void report_attr(ipp_attribute_t
*attr
);
69 static int report_printer_state(ipp_t
*ipp
, int job_id
);
72 static int run_pictwps_filter(char **argv
, const char *filename
);
73 #endif /* __APPLE__ */
74 static void sigterm_handler(int sig
);
78 * 'main()' - Send a file to the printer or server.
82 * printer-uri job-id user title copies options [file]
85 int /* O - Exit status */
86 main(int argc
, /* I - Number of command-line args */
87 char *argv
[]) /* I - Command-line arguments */
89 int i
; /* Looping var */
90 int send_options
; /* Send job options? */
91 int num_options
; /* Number of printer options */
92 cups_option_t
*options
; /* Printer options */
93 const char *device_uri
; /* Device URI */
94 char scheme
[255], /* Scheme in URI */
95 hostname
[1024], /* Hostname */
96 username
[255], /* Username info */
97 resource
[1024], /* Resource info (printer name) */
98 addrname
[256], /* Address name */
99 *optptr
, /* Pointer to URI options */
100 *name
, /* Name of option */
101 *value
, /* Value of option */
102 sep
; /* Separator character */
103 int snmp_fd
, /* SNMP socket */
104 start_count
, /* Page count via SNMP at start */
105 page_count
, /* Page count via SNMP */
106 have_supplies
; /* Printer supports supply levels? */
107 int num_files
; /* Number of files to print */
108 char **files
, /* Files to print */
109 *filename
; /* Pointer to single filename */
110 int port
; /* Port number (not used) */
111 char uri
[HTTP_MAX_URI
]; /* Updated URI without user/pass */
112 ipp_status_t ipp_status
; /* Status of IPP request */
113 http_t
*http
; /* HTTP connection */
114 ipp_t
*request
, /* IPP request */
115 *response
, /* IPP response */
116 *supported
; /* get-printer-attributes response */
117 time_t start_time
; /* Time of first connect */
118 int recoverable
; /* Recoverable error shown? */
119 int contimeout
; /* Connection timeout */
120 int delay
; /* Delay for retries... */
121 int compression
, /* Do compression of the job data? */
122 waitjob
, /* Wait for job complete? */
123 waitprinter
; /* Wait for printer ready? */
124 ipp_attribute_t
*job_id_attr
; /* job-id attribute */
125 int job_id
; /* job-id value */
126 ipp_attribute_t
*job_sheets
; /* job-media-sheets-completed */
127 ipp_attribute_t
*job_state
; /* job-state */
128 ipp_attribute_t
*copies_sup
; /* copies-supported */
129 ipp_attribute_t
*format_sup
; /* document-format-supported */
130 ipp_attribute_t
*printer_state
; /* printer-state attribute */
131 ipp_attribute_t
*printer_accepting
; /* printer-is-accepting-jobs */
132 int copies
, /* Number of copies for job */
133 copies_remaining
; /* Number of copies remaining */
134 const char *content_type
, /* CONTENT_TYPE environment variable */
135 *final_content_type
; /* FINAL_CONTENT_TYPE environment var */
136 #if defined(HAVE_SIGACTION) && !defined(HAVE_SIGSET)
137 struct sigaction action
; /* Actions for POSIX signals */
138 #endif /* HAVE_SIGACTION && !HAVE_SIGSET */
139 int version
; /* IPP version */
140 static const char * const pattrs
[] =
141 { /* Printer attributes we want */
142 "com.apple.print.recoverable-message",
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,
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...
520 start_time
= time(NULL
);
522 fputs("STATE: +connecting-to-device\n", stderr
);
526 fprintf(stderr
, "DEBUG: Connecting to %s:%d\n", hostname
, port
);
527 _cupsLangPuts(stderr
, _("INFO: Connecting to printer...\n"));
529 if ((http
= httpConnectEncrypt(hostname
, port
, cupsEncryption())) == NULL
)
534 if (getenv("CLASS") != NULL
)
537 * If the CLASS environment variable is set, the job was submitted
538 * to a class and not to a specific queue. In this case, we want
539 * to abort immediately so that the job can be requeued on the next
540 * available printer in the class.
543 _cupsLangPuts(stderr
,
544 _("INFO: Unable to contact printer, queuing on next "
545 "printer in class...\n"));
551 * Sleep 5 seconds to keep the job from requeuing too rapidly...
556 return (CUPS_BACKEND_FAILED
);
559 if (errno
== ECONNREFUSED
|| errno
== EHOSTDOWN
||
560 errno
== EHOSTUNREACH
)
562 if (contimeout
&& (time(NULL
) - start_time
) > contimeout
)
564 _cupsLangPuts(stderr
, _("ERROR: Printer not responding!\n"));
565 return (CUPS_BACKEND_FAILED
);
570 _cupsLangPrintf(stderr
,
571 _("WARNING: recoverable: Network host \'%s\' is busy; "
572 "will retry in %d seconds...\n"),
582 _cupsLangPrintf(stderr
, _("ERROR: Unable to locate printer \'%s\'!\n"),
584 return (CUPS_BACKEND_STOP
);
590 fprintf(stderr
, "DEBUG: Connection error: %s\n", strerror(errno
));
591 _cupsLangPuts(stderr
,
592 _("ERROR: recoverable: Unable to connect to printer; will "
593 "retry in 30 seconds...\n"));
601 while (http
== NULL
);
603 if (job_cancelled
|| !http
)
608 return (CUPS_BACKEND_FAILED
);
611 fputs("STATE: -connecting-to-device\n", stderr
);
612 _cupsLangPuts(stderr
, _("INFO: Connected to printer...\n"));
615 if (http
->hostaddr
->addr
.sa_family
== AF_INET6
)
616 fprintf(stderr
, "DEBUG: Connected to [%s]:%d (IPv6)...\n",
617 httpAddrString(http
->hostaddr
, addrname
, sizeof(addrname
)),
618 ntohs(http
->hostaddr
->ipv6
.sin6_port
));
620 #endif /* AF_INET6 */
621 if (http
->hostaddr
->addr
.sa_family
== AF_INET
)
622 fprintf(stderr
, "DEBUG: Connected to %s:%d (IPv4)...\n",
623 httpAddrString(http
->hostaddr
, addrname
, sizeof(addrname
)),
624 ntohs(http
->hostaddr
->ipv4
.sin_port
));
627 * See if the printer supports SNMP...
630 if ((snmp_fd
= _cupsSNMPOpen(http
->hostaddr
->addr
.sa_family
)) >= 0)
631 have_supplies
= !backendSNMPSupplies(snmp_fd
, http
->hostaddr
, &start_count
,
634 have_supplies
= start_count
= 0;
637 * Build a URI for the printer and fill the standard IPP attributes for
638 * an IPP_PRINT_FILE request. We can't use the URI in argv[0] because it
639 * might contain username:password information...
642 httpAssembleURI(HTTP_URI_CODING_ALL
, uri
, sizeof(uri
), scheme
, NULL
, hostname
,
646 * First validate the destination and see if the device supports multiple
647 * copies. We have to do this because some IPP servers (e.g. HP JetDirect)
648 * don't support the copies attribute...
658 * Check for side-channel requests...
661 backendCheckSideChannel(snmp_fd
, http
->hostaddr
);
664 * Build the IPP request...
667 request
= ippNewRequest(IPP_GET_PRINTER_ATTRIBUTES
);
668 request
->request
.op
.version
[0] = version
/ 10;
669 request
->request
.op
.version
[1] = version
% 10;
671 ippAddString(request
, IPP_TAG_OPERATION
, IPP_TAG_URI
, "printer-uri",
674 ippAddStrings(request
, IPP_TAG_OPERATION
, IPP_TAG_KEYWORD
,
675 "requested-attributes", sizeof(pattrs
) / sizeof(pattrs
[0]),
682 fputs("DEBUG: Getting supported attributes...\n", stderr
);
684 if (http
->version
< HTTP_1_1
)
687 if ((supported
= cupsDoRequest(http
, request
, resource
)) == NULL
)
688 ipp_status
= cupsLastError();
690 ipp_status
= supported
->request
.status
.status_code
;
692 if (ipp_status
> IPP_OK_CONFLICT
)
694 if (ipp_status
== IPP_PRINTER_BUSY
||
695 ipp_status
== IPP_SERVICE_UNAVAILABLE
)
697 if (contimeout
&& (time(NULL
) - start_time
) > contimeout
)
699 _cupsLangPuts(stderr
, _("ERROR: Printer not responding!\n"));
700 return (CUPS_BACKEND_FAILED
);
705 _cupsLangPrintf(stderr
,
706 _("WARNING: recoverable: Network host \'%s\' is busy; "
707 "will retry in %d seconds...\n"),
710 report_printer_state(supported
, 0);
717 else if ((ipp_status
== IPP_BAD_REQUEST
||
718 ipp_status
== IPP_VERSION_NOT_SUPPORTED
) && version
> 10)
721 * Switch to IPP/1.0...
724 _cupsLangPrintf(stderr
,
725 _("INFO: Printer does not support IPP/%d.%d, trying "
726 "IPP/1.0...\n"), version
/ 10, version
% 10);
730 else if (ipp_status
== IPP_NOT_FOUND
)
732 _cupsLangPuts(stderr
, _("ERROR: Destination printer does not exist!\n"));
735 ippDelete(supported
);
737 return (CUPS_BACKEND_STOP
);
741 _cupsLangPrintf(stderr
,
742 _("ERROR: Unable to get printer status (%s)!\n"),
743 cupsLastErrorString());
748 ippDelete(supported
);
752 else if ((copies_sup
= ippFindAttribute(supported
, "copies-supported",
753 IPP_TAG_RANGE
)) != NULL
)
756 * Has the "copies-supported" attribute - does it have an upper
760 if (copies_sup
->values
[0].range
.upper
<= 1)
761 copies_sup
= NULL
; /* No */
764 format_sup
= ippFindAttribute(supported
, "document-format-supported",
769 fprintf(stderr
, "DEBUG: document-format-supported (%d values)\n",
770 format_sup
->num_values
);
771 for (i
= 0; i
< format_sup
->num_values
; i
++)
772 fprintf(stderr
, "DEBUG: [%d] = \"%s\"\n", i
,
773 format_sup
->values
[i
].string
.text
);
776 report_printer_state(supported
, 0);
778 while (ipp_status
> IPP_OK_CONFLICT
);
781 * See if the printer is accepting jobs and is not stopped; if either
782 * condition is true and we are printing to a class, requeue the job...
785 if (getenv("CLASS") != NULL
)
787 printer_state
= ippFindAttribute(supported
, "printer-state",
789 printer_accepting
= ippFindAttribute(supported
, "printer-is-accepting-jobs",
792 if (printer_state
== NULL
||
793 (printer_state
->values
[0].integer
> IPP_PRINTER_PROCESSING
&&
795 printer_accepting
== NULL
||
796 !printer_accepting
->values
[0].boolean
)
799 * If the CLASS environment variable is set, the job was submitted
800 * to a class and not to a specific queue. In this case, we want
801 * to abort immediately so that the job can be requeued on the next
802 * available printer in the class.
805 _cupsLangPuts(stderr
,
806 _("INFO: Unable to contact printer, queuing on next "
807 "printer in class...\n"));
809 ippDelete(supported
);
816 * Sleep 5 seconds to keep the job from requeuing too rapidly...
821 return (CUPS_BACKEND_FAILED
);
828 * If we've shown a recoverable error make sure the printer proxies
829 * have a chance to see the recovered message. Not pretty but
830 * necessary for now...
833 fputs("INFO: recovered: \n", stderr
);
838 * See if the printer supports multiple copies...
841 copies
= atoi(argv
[4]);
843 if (copies_sup
|| argc
< 7)
845 copies_remaining
= 1;
851 copies_remaining
= copies
;
854 * Then issue the print-job request...
859 while (copies_remaining
> 0)
862 * Check for side-channel requests...
865 backendCheckSideChannel(snmp_fd
, http
->hostaddr
);
868 * Build the IPP request...
875 request
= ippNewRequest(IPP_CREATE_JOB
);
877 request
= ippNewRequest(IPP_PRINT_JOB
);
879 request
->request
.op
.version
[0] = version
/ 10;
880 request
->request
.op
.version
[1] = version
% 10;
882 ippAddString(request
, IPP_TAG_OPERATION
, IPP_TAG_URI
, "printer-uri",
885 fprintf(stderr
, "DEBUG: printer-uri = \"%s\"\n", uri
);
888 ippAddString(request
, IPP_TAG_OPERATION
, IPP_TAG_NAME
,
889 "requesting-user-name", NULL
, argv
[2]);
891 fprintf(stderr
, "DEBUG: requesting-user-name = \"%s\"\n", argv
[2]);
894 * Only add a "job-name" attribute if the remote server supports
895 * copy generation - some IPP implementations like HP's don't seem
896 * to like UTF-8 job names (STR #1837)...
899 if (argv
[3][0] && copies_sup
)
900 ippAddString(request
, IPP_TAG_OPERATION
, IPP_TAG_NAME
, "job-name", NULL
,
903 fprintf(stderr
, "DEBUG: job-name = \"%s\"\n", argv
[3]);
907 ippAddString(request
, IPP_TAG_OPERATION
, IPP_TAG_KEYWORD
,
908 "compression", NULL
, "gzip");
909 #endif /* HAVE_LIBZ */
912 * Handle options on the command-line...
916 num_options
= cupsParseOptions(argv
[5], 0, &options
);
919 if (!strcasecmp(final_content_type
, "application/pictwps") &&
922 if (format_sup
!= NULL
)
924 for (i
= 0; i
< format_sup
->num_values
; i
++)
925 if (!strcasecmp(final_content_type
, format_sup
->values
[i
].string
.text
))
929 if (format_sup
== NULL
|| i
>= format_sup
->num_values
)
932 * Remote doesn't support "application/pictwps" (i.e. it's not MacOS X)
933 * so convert the document to PostScript...
936 if (run_pictwps_filter(argv
, files
[0]))
944 return (CUPS_BACKEND_FAILED
);
947 files
[0] = pstmpname
;
950 * Change the MIME type to application/postscript and change the
951 * number of copies to 1...
954 final_content_type
= "application/postscript";
956 copies_remaining
= 1;
960 #endif /* __APPLE__ */
962 if (format_sup
!= NULL
)
964 for (i
= 0; i
< format_sup
->num_values
; i
++)
965 if (!strcasecmp(final_content_type
, format_sup
->values
[i
].string
.text
))
968 if (i
< format_sup
->num_values
)
969 ippAddString(request
, IPP_TAG_OPERATION
, IPP_TAG_MIMETYPE
,
970 "document-format", NULL
, final_content_type
);
973 if (copies_sup
&& version
> 10 && send_options
)
976 * Only send options if the destination printer supports the copies
977 * attribute and IPP/1.1. This is a hack for the HP and Lexmark
978 * implementations of IPP, which do not accept extension attributes
979 * and incorrectly report a client-error-bad-request error instead of
980 * the successful-ok-unsupported-attributes status. In short, at least
981 * some HP and Lexmark implementations of IPP are non-compliant.
984 cupsEncodeOptions(request
, num_options
, options
);
986 ippAddInteger(request
, IPP_TAG_JOB
, IPP_TAG_INTEGER
, "copies",
990 cupsFreeOptions(num_options
, options
);
993 * If copies aren't supported, then we are likely dealing with an HP
994 * JetDirect. The HP IPP implementation seems to close the connection
995 * after every request - that is, it does *not* implement HTTP Keep-
996 * Alive, which is REQUIRED by HTTP/1.1...
1000 httpReconnect(http
);
1006 if (http
->version
< HTTP_1_1
)
1007 httpReconnect(http
);
1010 response
= cupsDoRequest(http
, request
, resource
);
1012 response
= cupsDoFileRequest(http
, request
, resource
, files
[0]);
1014 ipp_status
= cupsLastError();
1016 if (ipp_status
> IPP_OK_CONFLICT
)
1023 if (ipp_status
== IPP_SERVICE_UNAVAILABLE
||
1024 ipp_status
== IPP_PRINTER_BUSY
)
1026 _cupsLangPuts(stderr
,
1027 _("INFO: Printer busy; will retry in 10 seconds...\n"));
1030 else if ((ipp_status
== IPP_BAD_REQUEST
||
1031 ipp_status
== IPP_VERSION_NOT_SUPPORTED
) && version
> 10)
1034 * Switch to IPP/1.0...
1037 _cupsLangPrintf(stderr
,
1038 _("INFO: Printer does not support IPP/%d.%d, trying "
1039 "IPP/1.0...\n"), version
/ 10, version
% 10);
1041 httpReconnect(http
);
1046 * Update auth-info-required as needed...
1049 _cupsLangPrintf(stderr
, _("ERROR: Print file was not accepted (%s)!\n"),
1050 cupsLastErrorString());
1052 if (ipp_status
== IPP_NOT_AUTHORIZED
)
1054 fprintf(stderr
, "DEBUG: WWW-Authenticate=\"%s\"\n",
1055 httpGetField(http
, HTTP_FIELD_WWW_AUTHENTICATE
));
1057 if (!strncmp(httpGetField(http
, HTTP_FIELD_WWW_AUTHENTICATE
),
1059 fputs("ATTR: auth-info-required=negotiate\n", stderr
);
1061 fputs("ATTR: auth-info-required=username,password\n", stderr
);
1065 else if ((job_id_attr
= ippFindAttribute(response
, "job-id",
1066 IPP_TAG_INTEGER
)) == NULL
)
1068 _cupsLangPuts(stderr
,
1069 _("NOTICE: Print file accepted - job ID unknown.\n"));
1074 job_id
= job_id_attr
->values
[0].integer
;
1075 _cupsLangPrintf(stderr
, _("NOTICE: Print file accepted - job ID %d.\n"),
1079 ippDelete(response
);
1084 if (job_id
&& num_files
> 1)
1086 for (i
= 0; i
< num_files
; i
++)
1089 * Check for side-channel requests...
1092 backendCheckSideChannel(snmp_fd
, http
->hostaddr
);
1095 * Send the next file in the job...
1098 request
= ippNewRequest(IPP_SEND_DOCUMENT
);
1099 request
->request
.op
.version
[0] = version
/ 10;
1100 request
->request
.op
.version
[1] = version
% 10;
1102 ippAddString(request
, IPP_TAG_OPERATION
, IPP_TAG_URI
, "printer-uri",
1105 ippAddInteger(request
, IPP_TAG_OPERATION
, IPP_TAG_INTEGER
, "job-id",
1109 ippAddString(request
, IPP_TAG_OPERATION
, IPP_TAG_NAME
,
1110 "requesting-user-name", NULL
, argv
[2]);
1112 if ((i
+ 1) == num_files
)
1113 ippAddBoolean(request
, IPP_TAG_OPERATION
, "last-document", 1);
1115 ippAddString(request
, IPP_TAG_OPERATION
, IPP_TAG_MIMETYPE
,
1116 "document-format", NULL
, content_type
);
1118 if (http
->version
< HTTP_1_1
)
1119 httpReconnect(http
);
1121 ippDelete(cupsDoFileRequest(http
, request
, resource
, files
[i
]));
1123 if (cupsLastError() > IPP_OK_CONFLICT
)
1125 ipp_status
= cupsLastError();
1127 _cupsLangPrintf(stderr
,
1128 _("ERROR: Unable to add file %d to job: %s\n"),
1129 job_id
, cupsLastErrorString());
1135 if (ipp_status
<= IPP_OK_CONFLICT
&& argc
> 6)
1137 fprintf(stderr
, "PAGE: 1 %d\n", copies_sup
? atoi(argv
[4]) : 1);
1138 copies_remaining
--;
1140 else if (ipp_status
== IPP_SERVICE_UNAVAILABLE
||
1141 ipp_status
== IPP_PRINTER_BUSY
)
1144 copies_remaining
--;
1147 * Wait for the job to complete...
1150 if (!job_id
|| !waitjob
)
1153 _cupsLangPuts(stderr
, _("INFO: Waiting for job to complete...\n"));
1155 for (delay
= 1; !job_cancelled
;)
1158 * Check for side-channel requests...
1161 backendCheckSideChannel(snmp_fd
, http
->hostaddr
);
1164 * Build an IPP_GET_JOB_ATTRIBUTES request...
1167 request
= ippNewRequest(IPP_GET_JOB_ATTRIBUTES
);
1168 request
->request
.op
.version
[0] = version
/ 10;
1169 request
->request
.op
.version
[1] = version
% 10;
1171 ippAddString(request
, IPP_TAG_OPERATION
, IPP_TAG_URI
, "printer-uri",
1174 ippAddInteger(request
, IPP_TAG_OPERATION
, IPP_TAG_INTEGER
, "job-id",
1178 ippAddString(request
, IPP_TAG_OPERATION
, IPP_TAG_NAME
,
1179 "requesting-user-name", NULL
, argv
[2]);
1181 ippAddStrings(request
, IPP_TAG_OPERATION
, IPP_TAG_KEYWORD
,
1182 "requested-attributes", sizeof(jattrs
) / sizeof(jattrs
[0]),
1189 if (!copies_sup
|| http
->version
< HTTP_1_1
)
1190 httpReconnect(http
);
1192 response
= cupsDoRequest(http
, request
, resource
);
1193 ipp_status
= cupsLastError();
1195 if (ipp_status
== IPP_NOT_FOUND
)
1198 * Job has gone away and/or the server has no job history...
1201 ippDelete(response
);
1203 ipp_status
= IPP_OK
;
1207 if (ipp_status
> IPP_OK_CONFLICT
)
1209 if (ipp_status
!= IPP_SERVICE_UNAVAILABLE
&&
1210 ipp_status
!= IPP_PRINTER_BUSY
)
1212 ippDelete(response
);
1214 _cupsLangPrintf(stderr
,
1215 _("ERROR: Unable to get job %d attributes (%s)!\n"),
1216 job_id
, cupsLastErrorString());
1223 if ((job_state
= ippFindAttribute(response
, "job-state",
1224 IPP_TAG_ENUM
)) != NULL
)
1227 * Stop polling if the job is finished or pending-held...
1230 if (job_state
->values
[0].integer
> IPP_JOB_STOPPED
)
1232 if ((job_sheets
= ippFindAttribute(response
,
1233 "job-media-sheets-completed",
1234 IPP_TAG_INTEGER
)) != NULL
)
1235 fprintf(stderr
, "PAGE: total %d\n",
1236 job_sheets
->values
[0].integer
);
1238 ippDelete(response
);
1245 * If the printer does not return a job-state attribute, it does not
1246 * conform to the IPP specification - break out immediately and fail
1250 fputs("DEBUG: No job-state available from printer - stopping queue.\n",
1252 ipp_status
= IPP_INTERNAL_ERROR
;
1257 ippDelete(response
);
1260 * Check the printer state and report it if necessary...
1263 check_printer_state(http
, uri
, resource
, argv
[2], version
, job_id
);
1266 * Wait 1-10 seconds before polling again...
1278 * Cancel the job as needed...
1281 if (job_cancelled
&& job_id
)
1282 cancel_job(http
, uri
, job_id
, resource
, argv
[2], version
);
1285 * Check the printer state and report it if necessary...
1288 check_printer_state(http
, uri
, resource
, argv
[2], version
, job_id
);
1291 * Collect the final page count as needed...
1294 if (have_supplies
&&
1295 !backendSNMPSupplies(snmp_fd
, http
->hostaddr
, &page_count
, NULL
) &&
1296 page_count
> start_count
)
1297 fprintf(stderr
, "PAGE: total %d\n", page_count
- start_count
);
1305 ippDelete(supported
);
1308 * Remove the temporary file(s) if necessary...
1312 unlink(tmpfilename
);
1317 for (i
= 0; i
< num_files
; i
++)
1320 #endif /* HAVE_LIBZ */
1325 #endif /* __APPLE__ */
1328 * Return the queue status...
1331 if (ipp_status
== IPP_NOT_AUTHORIZED
)
1332 return (CUPS_BACKEND_AUTH_REQUIRED
);
1333 else if (ipp_status
== IPP_INTERNAL_ERROR
)
1334 return (CUPS_BACKEND_STOP
);
1335 else if (ipp_status
> IPP_OK_CONFLICT
)
1336 return (CUPS_BACKEND_FAILED
);
1338 return (CUPS_BACKEND_OK
);
1343 * 'cancel_job()' - Cancel a print job.
1347 cancel_job(http_t
*http
, /* I - HTTP connection */
1348 const char *uri
, /* I - printer-uri */
1349 int id
, /* I - job-id */
1350 const char *resource
, /* I - Resource path */
1351 const char *user
, /* I - requesting-user-name */
1352 int version
) /* I - IPP version */
1354 ipp_t
*request
; /* Cancel-Job request */
1357 _cupsLangPuts(stderr
, _("INFO: Canceling print job...\n"));
1359 request
= ippNewRequest(IPP_CANCEL_JOB
);
1360 request
->request
.op
.version
[0] = version
/ 10;
1361 request
->request
.op
.version
[1] = version
% 10;
1363 ippAddString(request
, IPP_TAG_OPERATION
, IPP_TAG_URI
, "printer-uri",
1365 ippAddInteger(request
, IPP_TAG_OPERATION
, IPP_TAG_INTEGER
, "job-id", id
);
1367 if (user
&& user
[0])
1368 ippAddString(request
, IPP_TAG_OPERATION
, IPP_TAG_NAME
,
1369 "requesting-user-name", NULL
, user
);
1375 if (http
->version
< HTTP_1_1
)
1376 httpReconnect(http
);
1378 ippDelete(cupsDoRequest(http
, request
, resource
));
1380 if (cupsLastError() > IPP_OK_CONFLICT
)
1381 _cupsLangPrintf(stderr
, _("ERROR: Unable to cancel job %d: %s\n"), id
,
1382 cupsLastErrorString());
1387 * 'check_printer_state()' - Check the printer state...
1391 check_printer_state(
1392 http_t
*http
, /* I - HTTP connection */
1393 const char *uri
, /* I - Printer URI */
1394 const char *resource
, /* I - Resource path */
1395 const char *user
, /* I - Username, if any */
1396 int version
, /* I - IPP version */
1397 int job_id
) /* I - Current job ID */
1399 ipp_t
*request
, /* IPP request */
1400 *response
; /* IPP response */
1401 static const char * const attrs
[] = /* Attributes we want */
1403 "com.apple.print.recoverable-message",
1409 "printer-state-message",
1410 "printer-state-reasons"
1415 * Check on the printer state...
1418 request
= ippNewRequest(IPP_GET_PRINTER_ATTRIBUTES
);
1419 request
->request
.op
.version
[0] = version
/ 10;
1420 request
->request
.op
.version
[1] = version
% 10;
1422 ippAddString(request
, IPP_TAG_OPERATION
, IPP_TAG_URI
, "printer-uri",
1425 if (user
&& user
[0])
1426 ippAddString(request
, IPP_TAG_OPERATION
, IPP_TAG_NAME
,
1427 "requesting-user-name", NULL
, user
);
1429 ippAddStrings(request
, IPP_TAG_OPERATION
, IPP_TAG_KEYWORD
,
1430 "requested-attributes",
1431 (int)(sizeof(attrs
) / sizeof(attrs
[0])), NULL
, attrs
);
1437 if (http
->version
< HTTP_1_1
)
1438 httpReconnect(http
);
1440 if ((response
= cupsDoRequest(http
, request
, resource
)) != NULL
)
1442 report_printer_state(response
, job_id
);
1443 ippDelete(response
);
1450 * 'compress_files()' - Compress print files...
1454 compress_files(int num_files
, /* I - Number of files */
1455 char **files
) /* I - Files */
1457 int i
, /* Looping var */
1458 fd
; /* Temporary file descriptor */
1459 ssize_t bytes
; /* Bytes read/written */
1460 size_t total
; /* Total bytes read */
1461 cups_file_t
*in
, /* Input file */
1462 *out
; /* Output file */
1463 struct stat outinfo
; /* Output file information */
1464 char filename
[1024], /* Temporary filename */
1465 buffer
[32768]; /* Copy buffer */
1468 fprintf(stderr
, "DEBUG: Compressing %d job files...\n", num_files
);
1469 for (i
= 0; i
< num_files
; i
++)
1471 if ((fd
= cupsTempFd(filename
, sizeof(filename
))) < 0)
1473 _cupsLangPrintf(stderr
,
1474 _("ERROR: Unable to create temporary compressed print "
1475 "file: %s\n"), strerror(errno
));
1476 exit(CUPS_BACKEND_FAILED
);
1479 if ((out
= cupsFileOpenFd(fd
, "w9")) == NULL
)
1481 _cupsLangPrintf(stderr
,
1482 _("ERROR: Unable to open temporary compressed print "
1483 "file: %s\n"), strerror(errno
));
1484 exit(CUPS_BACKEND_FAILED
);
1487 if ((in
= cupsFileOpen(files
[i
], "r")) == NULL
)
1489 _cupsLangPrintf(stderr
,
1490 _("ERROR: Unable to open print file \"%s\": %s\n"),
1491 files
[i
], strerror(errno
));
1493 exit(CUPS_BACKEND_FAILED
);
1497 while ((bytes
= cupsFileRead(in
, buffer
, sizeof(buffer
))) > 0)
1498 if (cupsFileWrite(out
, buffer
, bytes
) < bytes
)
1500 _cupsLangPrintf(stderr
,
1501 _("ERROR: Unable to write %d bytes to \"%s\": %s\n"),
1502 (int)bytes
, filename
, strerror(errno
));
1505 exit(CUPS_BACKEND_FAILED
);
1513 files
[i
] = strdup(filename
);
1515 if (!stat(filename
, &outinfo
))
1517 "DEBUG: File %d compressed to %.1f%% of original size, "
1518 CUPS_LLFMT
" bytes...\n",
1519 i
+ 1, 100.0 * outinfo
.st_size
/ total
,
1520 CUPS_LLCAST outinfo
.st_size
);
1523 #endif /* HAVE_LIBZ */
1527 * 'password_cb()' - Disable the password prompt for cupsDoFileRequest().
1530 static const char * /* O - Password */
1531 password_cb(const char *prompt
) /* I - Prompt (not used) */
1535 if (password
&& *password
&& password_tries
< 3)
1544 * If there is no password set in the device URI, return the
1545 * "authentication required" exit code...
1549 unlink(tmpfilename
);
1554 #endif /* __APPLE__ */
1556 fputs("ATTR: auth-info-required=username,password\n", stderr
);
1558 exit(CUPS_BACKEND_AUTH_REQUIRED
);
1560 return (NULL
); /* Eliminate compiler warning */
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
*caprm
, /* com.apple.print.recoverable-message */
1644 *psm
, /* printer-state-message */
1645 *reasons
, /* printer-state-reasons */
1646 *marker
; /* marker-* attributes */
1647 const char *reason
; /* Current reason */
1648 const char *prefix
; /* Prefix for STATE: line */
1649 char state
[1024]; /* State string */
1650 int saw_caprw
; /* Saw com.apple.print.recoverable-warning state */
1653 if ((psm
= ippFindAttribute(ipp
, "printer-state-message",
1654 IPP_TAG_TEXT
)) != NULL
)
1655 fprintf(stderr
, "INFO: %s\n", psm
->values
[0].string
.text
);
1657 if ((reasons
= ippFindAttribute(ipp
, "printer-state-reasons",
1658 IPP_TAG_KEYWORD
)) == NULL
)
1665 for (i
= 0, count
= 0; i
< reasons
->num_values
; i
++)
1667 reason
= reasons
->values
[i
].string
.text
;
1669 if (!strcmp(reason
, "com.apple.print.recoverable-warning"))
1671 else if (strcmp(reason
, "paused"))
1673 strlcat(state
, prefix
, sizeof(state
));
1674 strlcat(state
, reason
, sizeof(state
));
1681 fprintf(stderr
, "%s\n", state
);
1684 * Relay com.apple.print.recoverable-message...
1687 if ((caprm
= ippFindAttribute(ipp
, "com.apple.print.recoverable-message",
1688 IPP_TAG_TEXT
)) != NULL
)
1689 fprintf(stderr
, "WARNING: %s: %s\n",
1690 saw_caprw
? "recoverable" : "recovered",
1691 caprm
->values
[0].string
.text
);
1694 * Relay the current marker-* attribute values...
1697 if ((marker
= ippFindAttribute(ipp
, "marker-colors", IPP_TAG_NAME
)) != NULL
)
1698 report_attr(marker
);
1699 if ((marker
= ippFindAttribute(ipp
, "marker-high-levels",
1700 IPP_TAG_INTEGER
)) != NULL
)
1701 report_attr(marker
);
1702 if ((marker
= ippFindAttribute(ipp
, "marker-levels",
1703 IPP_TAG_INTEGER
)) != NULL
)
1704 report_attr(marker
);
1705 if ((marker
= ippFindAttribute(ipp
, "marker-low-levels",
1706 IPP_TAG_INTEGER
)) != NULL
)
1707 report_attr(marker
);
1708 if ((marker
= ippFindAttribute(ipp
, "marker-message", IPP_TAG_TEXT
)) != NULL
)
1709 report_attr(marker
);
1710 if ((marker
= ippFindAttribute(ipp
, "marker-names", IPP_TAG_NAME
)) != NULL
)
1711 report_attr(marker
);
1712 if ((marker
= ippFindAttribute(ipp
, "marker-types", IPP_TAG_KEYWORD
)) != NULL
)
1713 report_attr(marker
);
1721 * 'run_pictwps_filter()' - Convert PICT files to PostScript when printing
1724 * This step is required because the PICT format is not documented and
1725 * subject to change, so developing a filter for other OS's is infeasible.
1726 * Also, fonts required by the PICT file need to be embedded on the
1727 * client side (which has the fonts), so we run the filter to get a
1728 * PostScript file for printing...
1731 static int /* O - Exit status of filter */
1732 run_pictwps_filter(char **argv
, /* I - Command-line arguments */
1733 const char *filename
)/* I - Filename */
1735 struct stat fileinfo
; /* Print file information */
1736 const char *ppdfile
; /* PPD file for destination printer */
1737 int pid
; /* Child process ID */
1738 int fd
; /* Temporary file descriptor */
1739 int status
; /* Exit status of filter */
1740 const char *printer
; /* PRINTER env var */
1741 static char ppdenv
[1024]; /* PPD environment variable */
1745 * First get the PPD file for the printer...
1748 printer
= getenv("PRINTER");
1751 _cupsLangPuts(stderr
,
1752 _("ERROR: PRINTER environment variable not defined!\n"));
1756 if ((ppdfile
= cupsGetPPD(printer
)) == NULL
)
1758 _cupsLangPrintf(stderr
,
1759 _("ERROR: Unable to get PPD file for printer \"%s\" - "
1760 "%s.\n"), printer
, cupsLastErrorString());
1764 snprintf(ppdenv
, sizeof(ppdenv
), "PPD=%s", ppdfile
);
1769 * Then create a temporary file for printing...
1772 if ((fd
= cupsTempFd(pstmpname
, sizeof(pstmpname
))) < 0)
1774 _cupsLangPrintError(_("ERROR: Unable to create temporary file"));
1781 * Get the owner of the spool file - it is owned by the user we want to run
1786 stat(argv
[6], &fileinfo
);
1790 * Use the OSX defaults, as an up-stream filter created the PICT
1794 fileinfo
.st_uid
= 1;
1795 fileinfo
.st_gid
= 80;
1799 chown(ppdfile
, fileinfo
.st_uid
, fileinfo
.st_gid
);
1801 fchown(fd
, fileinfo
.st_uid
, fileinfo
.st_gid
);
1804 * Finally, run the filter to convert the file...
1807 if ((pid
= fork()) == 0)
1810 * Child process for pictwpstops... Redirect output of pictwpstops to a
1820 * Change to an unpriviledged user...
1823 setgid(fileinfo
.st_gid
);
1824 setuid(fileinfo
.st_uid
);
1827 execlp("pictwpstops", printer
, argv
[1], argv
[2], argv
[3], argv
[4], argv
[5],
1829 _cupsLangPrintf(stderr
, _("ERROR: Unable to exec pictwpstops: %s\n"),
1842 _cupsLangPrintf(stderr
, _("ERROR: Unable to fork pictwpstops: %s\n"),
1850 * Now wait for the filter to complete...
1853 if (wait(&status
) < 0)
1855 _cupsLangPrintf(stderr
, _("ERROR: Unable to wait for pictwpstops: %s\n"),
1871 _cupsLangPrintf(stderr
, _("ERROR: pictwpstops exited with status %d!\n"),
1874 _cupsLangPrintf(stderr
, _("ERROR: pictwpstops exited on signal %d!\n"),
1881 * Return with no errors..
1886 #endif /* __APPLE__ */
1890 * 'sigterm_handler()' - Handle 'terminate' signals that stop the backend.
1894 sigterm_handler(int sig
) /* I - Signal */
1896 (void)sig
; /* remove compiler warnings... */
1901 * Flag that the job should be cancelled...
1909 * The scheduler already tried to cancel us once, now just terminate
1910 * after removing our temp files!
1914 unlink(tmpfilename
);
1919 #endif /* __APPLE__ */
1926 * End of "$Id: ipp.c 7948 2008-09-17 00:04:12Z mike $".