2 * "$Id: ipp.c 6582 2007-06-20 22:07:38Z mike $"
4 * IPP backend for the Common UNIX Printing System (CUPS).
6 * Copyright 1997-2007 by Easy Software Products, all rights reserved.
8 * These coded instructions, statements, and computer programs are the
9 * property of Easy Software Products and are protected by Federal
10 * copyright law. Distribution and use rights are outlined in the file
11 * "LICENSE" which should have been included with this file. If this
12 * file is missing or damaged please contact Easy Software Products
15 * Attn: CUPS Licensing Information
16 * Easy Software Products
17 * 44141 Airport View Drive, Suite 204
18 * Hollywood, Maryland 20636 USA
20 * Voice: (301) 373-9600
21 * EMail: cups-info@cups.org
22 * WWW: http://www.cups.org
24 * This file is subject to the Apple OS-Developed Software exception.
28 * main() - Send a file to the printer or server.
29 * cancel_job() - Cancel a print job.
30 * check_printer_state() - Check the printer state...
31 * compress_files() - Compress print files...
32 * password_cb() - Disable the password prompt for
33 * cupsDoFileRequest().
34 * report_printer_state() - Report the printer state.
35 * run_pictwps_filter() - Convert PICT files to PostScript when printing
37 * sigterm_handler() - Handle 'terminate' signals that stop the backend.
41 * Include necessary headers.
44 #include <cups/http-private.h>
48 #include <sys/types.h>
50 #include <cups/backend.h>
51 #include <cups/cups.h>
52 #include <cups/language.h>
53 #include <cups/i18n.h>
54 #include <cups/string.h>
62 static char *password
= NULL
; /* Password for device URI */
64 static char pstmpname
[1024] = ""; /* Temporary PostScript file name */
65 #endif /* __APPLE__ */
66 static char tmpfilename
[1024] = ""; /* Temporary spool file name */
67 static int job_cancelled
= 0; /* Job cancelled? */
74 static void cancel_job(http_t
*http
, const char *uri
, int id
,
75 const char *resource
, const char *user
, int version
);
76 static void check_printer_state(http_t
*http
, const char *uri
,
77 const char *resource
, const char *user
,
80 static void compress_files(int num_files
, char **files
);
81 #endif /* HAVE_LIBZ */
82 static const char *password_cb(const char *);
83 static int report_printer_state(ipp_t
*ipp
);
86 static int run_pictwps_filter(char **argv
, const char *filename
);
87 #endif /* __APPLE__ */
88 static void sigterm_handler(int sig
);
92 * 'main()' - Send a file to the printer or server.
96 * printer-uri job-id user title copies options [file]
99 int /* O - Exit status */
100 main(int argc
, /* I - Number of command-line args */
101 char *argv
[]) /* I - Command-line arguments */
103 int i
; /* Looping var */
104 int send_options
; /* Send job options? */
105 int num_options
; /* Number of printer options */
106 cups_option_t
*options
; /* Printer options */
107 char method
[255], /* Method in URI */
108 hostname
[1024], /* Hostname */
109 username
[255], /* Username info */
110 resource
[1024], /* Resource info (printer name) */
111 addrname
[256], /* Address name */
112 *optptr
, /* Pointer to URI options */
113 name
[255], /* Name of option */
114 value
[255], /* Value of option */
115 *ptr
; /* Pointer into name or value */
116 int num_files
; /* Number of files to print */
117 char **files
, /* Files to print */
118 *filename
; /* Pointer to single filename */
119 int port
; /* Port number (not used) */
120 char uri
[HTTP_MAX_URI
]; /* Updated URI without user/pass */
121 ipp_status_t ipp_status
; /* Status of IPP request */
122 http_t
*http
; /* HTTP connection */
123 ipp_t
*request
, /* IPP request */
124 *response
, /* IPP response */
125 *supported
; /* get-printer-attributes response */
126 time_t start_time
; /* Time of first connect */
127 int recoverable
; /* Recoverable error shown? */
128 int contimeout
; /* Connection timeout */
129 int delay
; /* Delay for retries... */
130 int compression
, /* Do compression of the job data? */
131 waitjob
, /* Wait for job complete? */
132 waitprinter
; /* Wait for printer ready? */
133 ipp_attribute_t
*job_id_attr
; /* job-id attribute */
134 int job_id
; /* job-id value */
135 ipp_attribute_t
*job_sheets
; /* job-media-sheets-completed */
136 ipp_attribute_t
*job_state
; /* job-state */
137 ipp_attribute_t
*copies_sup
; /* copies-supported */
138 ipp_attribute_t
*format_sup
; /* document-format-supported */
139 ipp_attribute_t
*printer_state
; /* printer-state attribute */
140 ipp_attribute_t
*printer_accepting
; /* printer-is-accepting-jobs */
141 int copies
, /* Number of copies for job */
142 copies_remaining
; /* Number of copies remaining */
143 const char *content_type
; /* CONTENT_TYPE environment variable */
144 #if defined(HAVE_SIGACTION) && !defined(HAVE_SIGSET)
145 struct sigaction action
; /* Actions for POSIX signals */
146 #endif /* HAVE_SIGACTION && !HAVE_SIGSET */
147 int version
; /* IPP version */
148 static const char * const pattrs
[] =
149 { /* Printer attributes we want */
151 "document-format-supported",
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\" \"Internet Printing Protocol (%s)\"\n",
206 return (CUPS_BACKEND_OK
);
210 fprintf(stderr
, _("Usage: %s job-id user title copies options [file]\n"),
212 return (CUPS_BACKEND_STOP
);
216 * Get the (final) content type...
219 if ((content_type
= getenv("FINAL_CONTENT_TYPE")) == NULL
)
220 if ((content_type
= getenv("CONTENT_TYPE")) == NULL
)
221 content_type
= "application/octet-stream";
223 if (!strncmp(content_type
, "printer/", 8))
224 content_type
= "application/vnd.cups-raw";
227 * Extract the hostname and printer name from the URI...
230 if (httpSeparateURI(HTTP_URI_CODING_ALL
, cupsBackendDeviceURI(argv
),
231 method
, sizeof(method
), username
, sizeof(username
),
232 hostname
, sizeof(hostname
), &port
,
233 resource
, sizeof(resource
)) < HTTP_URI_OK
)
235 fputs(_("ERROR: Missing device URI on command-line and no "
236 "DEVICE_URI environment variable!\n"), stderr
);
237 return (CUPS_BACKEND_STOP
);
240 if (!strcmp(method
, "https"))
241 cupsSetEncryption(HTTP_ENCRYPT_ALWAYS
);
243 cupsSetEncryption(HTTP_ENCRYPT_IF_REQUESTED
);
246 * See if there are any options...
253 contimeout
= 7 * 24 * 60 * 60;
255 if ((optptr
= strchr(resource
, '?')) != NULL
)
258 * Yup, terminate the device name string and move to the first
259 * character of the optptr...
265 * Then parse the optptr...
274 for (ptr
= name
; *optptr
&& *optptr
!= '=';)
275 if (ptr
< (name
+ sizeof(name
) - 1))
287 for (ptr
= value
; *optptr
&& *optptr
!= '+' && *optptr
!= '&';)
288 if (ptr
< (value
+ sizeof(value
) - 1))
292 if (*optptr
== '+' || *optptr
== '&')
299 * Process the option...
302 if (!strcasecmp(name
, "waitjob"))
305 * Wait for job completion?
308 waitjob
= !strcasecmp(value
, "on") ||
309 !strcasecmp(value
, "yes") ||
310 !strcasecmp(value
, "true");
312 else if (!strcasecmp(name
, "waitprinter"))
315 * Wait for printer idle?
318 waitprinter
= !strcasecmp(value
, "on") ||
319 !strcasecmp(value
, "yes") ||
320 !strcasecmp(value
, "true");
322 else if (!strcasecmp(name
, "encryption"))
325 * Enable/disable encryption?
328 if (!strcasecmp(value
, "always"))
329 cupsSetEncryption(HTTP_ENCRYPT_ALWAYS
);
330 else if (!strcasecmp(value
, "required"))
331 cupsSetEncryption(HTTP_ENCRYPT_REQUIRED
);
332 else if (!strcasecmp(value
, "never"))
333 cupsSetEncryption(HTTP_ENCRYPT_NEVER
);
334 else if (!strcasecmp(value
, "ifrequested"))
335 cupsSetEncryption(HTTP_ENCRYPT_IF_REQUESTED
);
339 _("ERROR: Unknown encryption option value \"%s\"!\n"),
343 else if (!strcasecmp(name
, "version"))
345 if (!strcmp(value
, "1.0"))
347 else if (!strcmp(value
, "1.1"))
352 _("ERROR: Unknown version option value \"%s\"!\n"),
357 else if (!strcasecmp(name
, "compression"))
359 compression
= !strcasecmp(value
, "true") ||
360 !strcasecmp(value
, "yes") ||
361 !strcasecmp(value
, "on") ||
362 !strcasecmp(value
, "gzip");
364 #endif /* HAVE_LIBZ */
365 else if (!strcasecmp(name
, "contimeout"))
368 * Set the connection timeout...
372 contimeout
= atoi(value
);
380 fprintf(stderr
, _("ERROR: Unknown option \"%s\" with value \"%s\"!\n"),
387 * If we have 7 arguments, print the file named on the command-line.
388 * Otherwise, copy stdin to a temporary file and print the temporary
395 * Copy stdin to a temporary file...
398 int fd
; /* File descriptor */
399 cups_file_t
*fp
; /* Temporary file */
400 char buffer
[8192]; /* Buffer for copying */
401 int bytes
; /* Number of bytes read */
404 if ((fd
= cupsTempFd(tmpfilename
, sizeof(tmpfilename
))) < 0)
406 perror("ERROR: unable to create temporary file");
407 return (CUPS_BACKEND_FAILED
);
410 if ((fp
= cupsFileOpenFd(fd
, compression
? "w9" : "w")) == NULL
)
412 perror("ERROR: unable to open temporary file");
415 return (CUPS_BACKEND_FAILED
);
418 while ((bytes
= fread(buffer
, 1, sizeof(buffer
), stdin
)) > 0)
419 if (cupsFileWrite(fp
, buffer
, bytes
) < bytes
)
421 perror("ERROR: unable to write to temporary file");
424 return (CUPS_BACKEND_FAILED
);
430 * Point to the single file from stdin...
433 filename
= tmpfilename
;
442 * Point to the files on the command-line...
445 num_files
= argc
- 6;
448 send_options
= strncasecmp(content_type
, "application/vnd.cups-", 21) != 0;
452 compress_files(num_files
, files
);
453 #endif /* HAVE_LIBZ */
456 fprintf(stderr
, "DEBUG: %d files to send in job...\n", num_files
);
459 * Set the authentication info, if any...
462 cupsSetPasswordCB(password_cb
);
467 * Use authenticaion information in the device URI...
470 if ((password
= strchr(username
, ':')) != NULL
)
473 cupsSetUser(username
);
478 * Try loading authentication information from the environment.
481 if ((ptr
= getenv("AUTH_USERNAME")) != NULL
)
484 password
= getenv("AUTH_PASSWORD");
488 * Try connecting to the remote server...
493 start_time
= time(NULL
);
495 fputs("STATE: +connecting-to-device\n", stderr
);
499 fprintf(stderr
, _("INFO: Connecting to %s on port %d...\n"),
502 if ((http
= httpConnectEncrypt(hostname
, port
, cupsEncryption())) == NULL
)
507 if (getenv("CLASS") != NULL
)
510 * If the CLASS environment variable is set, the job was submitted
511 * to a class and not to a specific queue. In this case, we want
512 * to abort immediately so that the job can be requeued on the next
513 * available printer in the class.
516 fputs(_("INFO: Unable to contact printer, queuing on next "
517 "printer in class...\n"), stderr
);
519 if (argc
== 6 || strcmp(filename
, argv
[6]))
523 * Sleep 5 seconds to keep the job from requeuing too rapidly...
528 return (CUPS_BACKEND_FAILED
);
531 if (errno
== ECONNREFUSED
|| errno
== EHOSTDOWN
||
532 errno
== EHOSTUNREACH
)
534 if (contimeout
&& (time(NULL
) - start_time
) > contimeout
)
536 fputs(_("ERROR: Printer not responding!\n"), stderr
);
537 return (CUPS_BACKEND_FAILED
);
543 _("WARNING: recoverable: Network host \'%s\' is busy; will "
544 "retry in %d seconds...\n"),
554 fprintf(stderr
, _("ERROR: Unable to locate printer \'%s\'!\n"),
556 return (CUPS_BACKEND_STOP
);
562 fprintf(stderr
, "DEBUG: Connection error: %s\n", strerror(errno
));
563 fputs(_("ERROR: recoverable: Unable to connect to printer; will "
564 "retry in 30 seconds...\n"), stderr
);
572 while (http
== NULL
);
576 if (argc
== 6 || strcmp(filename
, argv
[6]))
579 return (CUPS_BACKEND_FAILED
);
582 fputs("STATE: -connecting-to-device\n", stderr
);
583 fprintf(stderr
, _("INFO: Connected to %s...\n"), hostname
);
586 if (http
->hostaddr
->addr
.sa_family
== AF_INET6
)
587 fprintf(stderr
, "DEBUG: Connected to [%s]:%d (IPv6)...\n",
588 httpAddrString(http
->hostaddr
, addrname
, sizeof(addrname
)),
589 ntohs(http
->hostaddr
->ipv6
.sin6_port
));
591 #endif /* AF_INET6 */
592 if (http
->hostaddr
->addr
.sa_family
== AF_INET
)
593 fprintf(stderr
, "DEBUG: Connected to %s:%d (IPv4)...\n",
594 httpAddrString(http
->hostaddr
, addrname
, sizeof(addrname
)),
595 ntohs(http
->hostaddr
->ipv4
.sin_port
));
598 * Build a URI for the printer and fill the standard IPP attributes for
599 * an IPP_PRINT_FILE request. We can't use the URI in argv[0] because it
600 * might contain username:password information...
603 snprintf(uri
, sizeof(uri
), "%s://%s:%d%s", method
, hostname
, port
, resource
);
606 * First validate the destination and see if the device supports multiple
607 * copies. We have to do this because some IPP servers (e.g. HP JetDirect)
608 * don't support the copies attribute...
618 * Build the IPP request...
621 request
= ippNewRequest(IPP_GET_PRINTER_ATTRIBUTES
);
622 request
->request
.op
.version
[1] = version
;
624 ippAddString(request
, IPP_TAG_OPERATION
, IPP_TAG_URI
, "printer-uri",
627 ippAddStrings(request
, IPP_TAG_OPERATION
, IPP_TAG_KEYWORD
,
628 "requested-attributes", sizeof(pattrs
) / sizeof(pattrs
[0]),
635 fputs("DEBUG: Getting supported attributes...\n", stderr
);
637 if ((supported
= cupsDoRequest(http
, request
, resource
)) == NULL
)
638 ipp_status
= cupsLastError();
640 ipp_status
= supported
->request
.status
.status_code
;
642 if (ipp_status
> IPP_OK_CONFLICT
)
644 if (ipp_status
== IPP_PRINTER_BUSY
||
645 ipp_status
== IPP_SERVICE_UNAVAILABLE
)
647 if (contimeout
&& (time(NULL
) - start_time
) > contimeout
)
649 fputs(_("ERROR: Printer not responding!\n"), stderr
);
650 return (CUPS_BACKEND_FAILED
);
656 _("WARNING: recoverable: Network host \'%s\' is busy; will "
657 "retry in %d seconds...\n"),
660 report_printer_state(supported
);
667 else if ((ipp_status
== IPP_BAD_REQUEST
||
668 ipp_status
== IPP_VERSION_NOT_SUPPORTED
) && version
== 1)
671 * Switch to IPP/1.0...
674 fputs(_("INFO: Printer does not support IPP/1.1, trying IPP/1.0...\n"),
679 else if (ipp_status
== IPP_NOT_FOUND
)
681 fputs(_("ERROR: Destination printer does not exist!\n"), stderr
);
684 ippDelete(supported
);
686 return (CUPS_BACKEND_STOP
);
690 fprintf(stderr
, _("ERROR: Unable to get printer status (%s)!\n"),
691 cupsLastErrorString());
696 ippDelete(supported
);
700 else if ((copies_sup
= ippFindAttribute(supported
, "copies-supported",
701 IPP_TAG_RANGE
)) != NULL
)
704 * Has the "copies-supported" attribute - does it have an upper
708 if (copies_sup
->values
[0].range
.upper
<= 1)
709 copies_sup
= NULL
; /* No */
712 format_sup
= ippFindAttribute(supported
, "document-format-supported",
717 fprintf(stderr
, "DEBUG: document-format-supported (%d values)\n",
718 format_sup
->num_values
);
719 for (i
= 0; i
< format_sup
->num_values
; i
++)
720 fprintf(stderr
, "DEBUG: [%d] = \"%s\"\n", i
,
721 format_sup
->values
[i
].string
.text
);
724 report_printer_state(supported
);
726 while (ipp_status
> IPP_OK_CONFLICT
);
729 * See if the printer is accepting jobs and is not stopped; if either
730 * condition is true and we are printing to a class, requeue the job...
733 if (getenv("CLASS") != NULL
)
735 printer_state
= ippFindAttribute(supported
, "printer-state",
737 printer_accepting
= ippFindAttribute(supported
, "printer-is-accepting-jobs",
740 if (printer_state
== NULL
||
741 (printer_state
->values
[0].integer
> IPP_PRINTER_PROCESSING
&&
743 printer_accepting
== NULL
||
744 !printer_accepting
->values
[0].boolean
)
747 * If the CLASS environment variable is set, the job was submitted
748 * to a class and not to a specific queue. In this case, we want
749 * to abort immediately so that the job can be requeued on the next
750 * available printer in the class.
753 fputs(_("INFO: Unable to contact printer, queuing on next "
754 "printer in class...\n"), stderr
);
756 ippDelete(supported
);
759 if (argc
== 6 || strcmp(filename
, argv
[6]))
763 * Sleep 5 seconds to keep the job from requeuing too rapidly...
768 return (CUPS_BACKEND_FAILED
);
775 * If we've shown a recoverable error make sure the printer proxies
776 * have a chance to see the recovered message. Not pretty but
777 * necessary for now...
780 fputs("INFO: recovered: \n", stderr
);
785 * See if the printer supports multiple copies...
788 copies
= atoi(argv
[4]);
790 if (copies_sup
|| argc
< 7)
792 copies_remaining
= 1;
798 copies_remaining
= copies
;
801 * Then issue the print-job request...
806 while (copies_remaining
> 0)
809 * Build the IPP request...
816 request
= ippNewRequest(IPP_CREATE_JOB
);
818 request
= ippNewRequest(IPP_PRINT_JOB
);
820 request
->request
.op
.version
[1] = version
;
822 ippAddString(request
, IPP_TAG_OPERATION
, IPP_TAG_URI
, "printer-uri",
825 fprintf(stderr
, "DEBUG: printer-uri = \"%s\"\n", uri
);
828 ippAddString(request
, IPP_TAG_OPERATION
, IPP_TAG_NAME
,
829 "requesting-user-name", NULL
, argv
[2]);
831 fprintf(stderr
, "DEBUG: requesting-user-name = \"%s\"\n", argv
[2]);
834 * Only add a "job-name" attribute if the remote server supports
835 * copy generation - some IPP implementations like HP's don't seem
836 * to like UTF-8 job names (STR #1837)...
839 if (argv
[3][0] && copies_sup
)
840 ippAddString(request
, IPP_TAG_OPERATION
, IPP_TAG_NAME
, "job-name", NULL
,
843 fprintf(stderr
, "DEBUG: job-name = \"%s\"\n", argv
[3]);
847 ippAddString(request
, IPP_TAG_OPERATION
, IPP_TAG_KEYWORD
,
848 "compression", NULL
, "gzip");
849 #endif /* HAVE_LIBZ */
852 * Handle options on the command-line...
856 num_options
= cupsParseOptions(argv
[5], 0, &options
);
859 if (content_type
!= NULL
&&
860 !strcasecmp(content_type
, "application/pictwps") && num_files
== 1)
862 if (format_sup
!= NULL
)
864 for (i
= 0; i
< format_sup
->num_values
; i
++)
865 if (!strcasecmp(content_type
, format_sup
->values
[i
].string
.text
))
869 if (format_sup
== NULL
|| i
>= format_sup
->num_values
)
872 * Remote doesn't support "application/pictwps" (i.e. it's not MacOS X)
873 * so convert the document to PostScript...
876 if (run_pictwps_filter(argv
, filename
))
877 return (CUPS_BACKEND_FAILED
);
879 filename
= pstmpname
;
882 * Change the MIME type to application/postscript and change the
883 * number of copies to 1...
886 content_type
= "application/postscript";
888 copies_remaining
= 1;
892 #endif /* __APPLE__ */
894 if (content_type
!= NULL
&& format_sup
!= NULL
)
896 for (i
= 0; i
< format_sup
->num_values
; i
++)
897 if (!strcasecmp(content_type
, format_sup
->values
[i
].string
.text
))
900 if (i
< format_sup
->num_values
)
901 ippAddString(request
, IPP_TAG_OPERATION
, IPP_TAG_MIMETYPE
,
902 "document-format", NULL
, content_type
);
905 if (copies_sup
&& version
> 0 && send_options
)
908 * Only send options if the destination printer supports the copies
909 * attribute and IPP/1.1. This is a hack for the HP and Lexmark
910 * implementations of IPP, which do not accept extension attributes
911 * and incorrectly report a client-error-bad-request error instead of
912 * the successful-ok-unsupported-attributes status. In short, at least
913 * some HP and Lexmark implementations of IPP are non-compliant.
916 cupsEncodeOptions(request
, num_options
, options
);
918 ippAddInteger(request
, IPP_TAG_JOB
, IPP_TAG_INTEGER
, "copies",
922 cupsFreeOptions(num_options
, options
);
925 * If copies aren't supported, then we are likely dealing with an HP
926 * JetDirect. The HP IPP implementation seems to close the connection
927 * after every request - that is, it does *not* implement HTTP Keep-
928 * Alive, which is REQUIRED by HTTP/1.1...
939 response
= cupsDoRequest(http
, request
, resource
);
941 response
= cupsDoFileRequest(http
, request
, resource
, files
[0]);
943 ipp_status
= cupsLastError();
945 if (ipp_status
> IPP_OK_CONFLICT
)
952 if (ipp_status
== IPP_SERVICE_UNAVAILABLE
||
953 ipp_status
== IPP_PRINTER_BUSY
)
955 fputs(_("INFO: Printer busy; will retry in 10 seconds...\n"), stderr
);
958 else if ((ipp_status
== IPP_BAD_REQUEST
||
959 ipp_status
== IPP_VERSION_NOT_SUPPORTED
) && version
== 1)
962 * Switch to IPP/1.0...
965 fputs(_("INFO: Printer does not support IPP/1.1, trying IPP/1.0...\n"),
971 fprintf(stderr
, _("ERROR: Print file was not accepted (%s)!\n"),
972 cupsLastErrorString());
974 else if ((job_id_attr
= ippFindAttribute(response
, "job-id",
975 IPP_TAG_INTEGER
)) == NULL
)
977 fputs(_("NOTICE: Print file accepted - job ID unknown.\n"), stderr
);
982 job_id
= job_id_attr
->values
[0].integer
;
983 fprintf(stderr
, _("NOTICE: Print file accepted - job ID %d.\n"), job_id
);
991 if (job_id
&& num_files
> 1)
993 for (i
= 0; i
< num_files
; i
++)
995 request
= ippNewRequest(IPP_SEND_DOCUMENT
);
997 request
->request
.op
.version
[1] = version
;
999 ippAddString(request
, IPP_TAG_OPERATION
, IPP_TAG_URI
, "printer-uri",
1002 ippAddInteger(request
, IPP_TAG_OPERATION
, IPP_TAG_INTEGER
, "job-id",
1006 ippAddString(request
, IPP_TAG_OPERATION
, IPP_TAG_NAME
,
1007 "requesting-user-name", NULL
, argv
[2]);
1009 if ((i
+ 1) == num_files
)
1010 ippAddBoolean(request
, IPP_TAG_OPERATION
, "last-document", 1);
1012 ippAddString(request
, IPP_TAG_OPERATION
, IPP_TAG_MIMETYPE
,
1013 "document-format", NULL
, content_type
);
1015 ippDelete(cupsDoFileRequest(http
, request
, resource
, files
[i
]));
1017 if (cupsLastError() > IPP_OK_CONFLICT
)
1019 ipp_status
= cupsLastError();
1021 fprintf(stderr
, _("ERROR: Unable to add file %d to job: %s\n"),
1022 job_id
, cupsLastErrorString());
1028 if (ipp_status
<= IPP_OK_CONFLICT
&& argc
> 6)
1030 fprintf(stderr
, "PAGE: 1 %d\n", copies_sup
? atoi(argv
[4]) : 1);
1031 copies_remaining
--;
1033 else if (ipp_status
== IPP_SERVICE_UNAVAILABLE
||
1034 ipp_status
== IPP_PRINTER_BUSY
)
1037 copies_remaining
--;
1040 * Wait for the job to complete...
1043 if (!job_id
|| !waitjob
)
1046 fputs(_("INFO: Waiting for job to complete...\n"), stderr
);
1048 for (; !job_cancelled
;)
1051 * Build an IPP_GET_JOB_ATTRIBUTES request...
1054 request
= ippNewRequest(IPP_GET_JOB_ATTRIBUTES
);
1055 request
->request
.op
.version
[1] = version
;
1057 ippAddString(request
, IPP_TAG_OPERATION
, IPP_TAG_URI
, "printer-uri",
1060 ippAddInteger(request
, IPP_TAG_OPERATION
, IPP_TAG_INTEGER
, "job-id",
1064 ippAddString(request
, IPP_TAG_OPERATION
, IPP_TAG_NAME
,
1065 "requesting-user-name", NULL
, argv
[2]);
1067 ippAddStrings(request
, IPP_TAG_OPERATION
, IPP_TAG_KEYWORD
,
1068 "requested-attributes", sizeof(jattrs
) / sizeof(jattrs
[0]),
1076 httpReconnect(http
);
1078 response
= cupsDoRequest(http
, request
, resource
);
1079 ipp_status
= cupsLastError();
1081 if (ipp_status
== IPP_NOT_FOUND
)
1084 * Job has gone away and/or the server has no job history...
1087 ippDelete(response
);
1089 ipp_status
= IPP_OK
;
1093 if (ipp_status
> IPP_OK_CONFLICT
)
1095 if (ipp_status
!= IPP_SERVICE_UNAVAILABLE
&&
1096 ipp_status
!= IPP_PRINTER_BUSY
)
1098 ippDelete(response
);
1100 fprintf(stderr
, _("ERROR: Unable to get job %d attributes (%s)!\n"),
1101 job_id
, cupsLastErrorString());
1108 if ((job_state
= ippFindAttribute(response
, "job-state",
1109 IPP_TAG_ENUM
)) != NULL
)
1112 * Stop polling if the job is finished or pending-held...
1115 if (job_state
->values
[0].integer
> IPP_JOB_STOPPED
)
1117 if ((job_sheets
= ippFindAttribute(response
,
1118 "job-media-sheets-completed",
1119 IPP_TAG_INTEGER
)) != NULL
)
1120 fprintf(stderr
, "PAGE: total %d\n",
1121 job_sheets
->values
[0].integer
);
1123 ippDelete(response
);
1129 ippDelete(response
);
1132 * Check the printer state and report it if necessary...
1135 check_printer_state(http
, uri
, resource
, argv
[2], version
);
1138 * Wait 10 seconds before polling again...
1146 * Cancel the job as needed...
1149 if (job_cancelled
&& job_id
)
1150 cancel_job(http
, uri
, job_id
, resource
, argv
[2], version
);
1153 * Check the printer state and report it if necessary...
1156 check_printer_state(http
, uri
, resource
, argv
[2], version
);
1164 ippDelete(supported
);
1167 * Remove the temporary file(s) if necessary...
1171 unlink(tmpfilename
);
1176 for (i
= 0; i
< num_files
; i
++)
1179 #endif /* HAVE_LIBZ */
1184 #endif /* __APPLE__ */
1187 * Return the queue status...
1190 return (ipp_status
> IPP_OK_CONFLICT
? CUPS_BACKEND_FAILED
: CUPS_BACKEND_OK
);
1195 * 'cancel_job()' - Cancel a print job.
1199 cancel_job(http_t
*http
, /* I - HTTP connection */
1200 const char *uri
, /* I - printer-uri */
1201 int id
, /* I - job-id */
1202 const char *resource
, /* I - Resource path */
1203 const char *user
, /* I - requesting-user-name */
1204 int version
) /* I - IPP version */
1206 ipp_t
*request
; /* Cancel-Job request */
1209 fputs(_("INFO: Canceling print job...\n"), stderr
);
1211 request
= ippNewRequest(IPP_CANCEL_JOB
);
1212 request
->request
.op
.version
[1] = version
;
1214 ippAddString(request
, IPP_TAG_OPERATION
, IPP_TAG_URI
, "printer-uri",
1216 ippAddInteger(request
, IPP_TAG_OPERATION
, IPP_TAG_INTEGER
, "job-id", id
);
1218 if (user
&& user
[0])
1219 ippAddString(request
, IPP_TAG_OPERATION
, IPP_TAG_NAME
,
1220 "requesting-user-name", NULL
, user
);
1226 ippDelete(cupsDoRequest(http
, request
, resource
));
1228 if (cupsLastError() > IPP_OK_CONFLICT
)
1229 fprintf(stderr
, _("ERROR: Unable to cancel job %d: %s\n"), id
,
1230 cupsLastErrorString());
1235 * 'check_printer_state()' - Check the printer state...
1239 check_printer_state(
1240 http_t
*http
, /* I - HTTP connection */
1241 const char *uri
, /* I - Printer URI */
1242 const char *resource
, /* I - Resource path */
1243 const char *user
, /* I - Username, if any */
1244 int version
) /* I - IPP version */
1246 ipp_t
*request
, /* IPP request */
1247 *response
; /* IPP response */
1248 static const char * const attrs
[] = /* Attributes we want */
1250 "printer-state-message",
1251 "printer-state-reasons"
1256 * Check on the printer state...
1259 request
= ippNewRequest(IPP_GET_PRINTER_ATTRIBUTES
);
1260 request
->request
.op
.version
[1] = version
;
1262 ippAddString(request
, IPP_TAG_OPERATION
, IPP_TAG_URI
, "printer-uri",
1265 if (user
&& user
[0])
1266 ippAddString(request
, IPP_TAG_OPERATION
, IPP_TAG_NAME
,
1267 "requesting-user-name", NULL
, user
);
1269 ippAddStrings(request
, IPP_TAG_OPERATION
, IPP_TAG_KEYWORD
,
1270 "requested-attributes",
1271 (int)(sizeof(attrs
) / sizeof(attrs
[0])), NULL
, attrs
);
1277 if ((response
= cupsDoRequest(http
, request
, resource
)) != NULL
)
1279 report_printer_state(response
);
1280 ippDelete(response
);
1287 * 'compress_files()' - Compress print files...
1291 compress_files(int num_files
, /* I - Number of files */
1292 char **files
) /* I - Files */
1294 int i
, /* Looping var */
1295 fd
; /* Temporary file descriptor */
1296 ssize_t bytes
; /* Bytes read/written */
1297 size_t total
; /* Total bytes read */
1298 cups_file_t
*in
, /* Input file */
1299 *out
; /* Output file */
1300 struct stat outinfo
; /* Output file information */
1301 char filename
[1024], /* Temporary filename */
1302 buffer
[32768]; /* Copy buffer */
1305 fprintf(stderr
, "DEBUG: Compressing %d job files...\n", num_files
);
1306 for (i
= 0; i
< num_files
; i
++)
1308 if ((fd
= cupsTempFd(filename
, sizeof(filename
))) < 0)
1311 _("ERROR: Unable to create temporary compressed print file: "
1314 exit(CUPS_BACKEND_FAILED
);
1317 if ((out
= cupsFileOpenFd(fd
, "w9")) == NULL
)
1320 _("ERROR: Unable to open temporary compressed print file: %s\n"),
1322 exit(CUPS_BACKEND_FAILED
);
1325 if ((in
= cupsFileOpen(files
[i
], "r")) == NULL
)
1327 fprintf(stderr
, _("ERROR: Unable to open print file \"%s\": %s\n"),
1328 files
[i
], strerror(errno
));
1330 exit(CUPS_BACKEND_FAILED
);
1334 while ((bytes
= cupsFileRead(in
, buffer
, sizeof(buffer
))) > 0)
1335 if (cupsFileWrite(out
, buffer
, bytes
) < bytes
)
1337 fprintf(stderr
, _("ERROR: Unable to write %d bytes to \"%s\": %s\n"),
1338 (int)bytes
, filename
, strerror(errno
));
1341 exit(CUPS_BACKEND_FAILED
);
1349 files
[i
] = strdup(filename
);
1351 if (!stat(filename
, &outinfo
))
1353 "DEBUG: File %d compressed to %.1f%% of original size, "
1354 CUPS_LLFMT
" bytes...\n",
1355 i
+ 1, 100.0 * outinfo
.st_size
/ total
,
1356 CUPS_LLCAST outinfo
.st_size
);
1359 #endif /* HAVE_LIBZ */
1363 * 'password_cb()' - Disable the password prompt for cupsDoFileRequest().
1366 static const char * /* O - Password */
1367 password_cb(const char *prompt
) /* I - Prompt (not used) */
1376 * If there is no password set in the device URI, return the
1377 * "authentication required" exit code...
1381 unlink(tmpfilename
);
1386 #endif /* __APPLE__ */
1388 fputs("ATTR: auth-info-required=username,password\n", stderr
);
1390 exit(CUPS_BACKEND_AUTH_REQUIRED
);
1392 return (NULL
); /* Eliminate compiler warning */
1398 * 'report_printer_state()' - Report the printer state.
1401 static int /* O - Number of reasons shown */
1402 report_printer_state(ipp_t
*ipp
) /* I - IPP response */
1404 int i
; /* Looping var */
1405 int count
; /* Count of reasons shown... */
1406 ipp_attribute_t
*psm
, /* pritner-state-message */
1407 *reasons
; /* printer-state-reasons */
1408 const char *reason
; /* Current reason */
1409 const char *message
; /* Message to show */
1410 char unknown
[1024]; /* Unknown message string */
1411 const char *prefix
; /* Prefix for STATE: line */
1412 char state
[1024]; /* State string */
1415 if ((psm
= ippFindAttribute(ipp
, "printer-state-message",
1416 IPP_TAG_TEXT
)) != NULL
)
1417 fprintf(stderr
, "INFO: %s\n", psm
->values
[0].string
.text
);
1419 if ((reasons
= ippFindAttribute(ipp
, "printer-state-reasons",
1420 IPP_TAG_KEYWORD
)) == NULL
)
1426 for (i
= 0, count
= 0; i
< reasons
->num_values
; i
++)
1428 reason
= reasons
->values
[i
].string
.text
;
1430 strlcat(state
, prefix
, sizeof(state
));
1431 strlcat(state
, reason
, sizeof(state
));
1436 if (!strncmp(reason
, "media-needed", 12))
1437 message
= _("Media tray needs to be filled.");
1438 else if (!strncmp(reason
, "media-jam", 9))
1439 message
= _("Media jam!");
1440 else if (!strncmp(reason
, "moving-to-paused", 16) ||
1441 !strncmp(reason
, "paused", 6) ||
1442 !strncmp(reason
, "shutdown", 8))
1443 message
= _("Printer off-line.");
1444 else if (!strncmp(reason
, "toner-low", 9))
1445 message
= _("Toner low.");
1446 else if (!strncmp(reason
, "toner-empty", 11))
1447 message
= _("Out of toner!");
1448 else if (!strncmp(reason
, "cover-open", 10))
1449 message
= _("Cover open.");
1450 else if (!strncmp(reason
, "interlock-open", 14))
1451 message
= _("Interlock open.");
1452 else if (!strncmp(reason
, "door-open", 9))
1453 message
= _("Door open.");
1454 else if (!strncmp(reason
, "input-tray-missing", 18))
1455 message
= _("Media tray missing!");
1456 else if (!strncmp(reason
, "media-low", 9))
1457 message
= _("Media tray almost empty.");
1458 else if (!strncmp(reason
, "media-empty", 11))
1459 message
= _("Media tray empty!");
1460 else if (!strncmp(reason
, "output-tray-missing", 19))
1461 message
= _("Output tray missing!");
1462 else if (!strncmp(reason
, "output-area-almost-full", 23))
1463 message
= _("Output bin almost full.");
1464 else if (!strncmp(reason
, "output-area-full", 16))
1465 message
= _("Output bin full!");
1466 else if (!strncmp(reason
, "marker-supply-low", 17))
1467 message
= _("Ink/toner almost empty.");
1468 else if (!strncmp(reason
, "marker-supply-empty", 19))
1469 message
= _("Ink/toner empty!");
1470 else if (!strncmp(reason
, "marker-waste-almost-full", 24))
1471 message
= _("Ink/toner waste bin almost full.");
1472 else if (!strncmp(reason
, "marker-waste-full", 17))
1473 message
= _("Ink/toner waste bin full!");
1474 else if (!strncmp(reason
, "fuser-over-temp", 15))
1475 message
= _("Fuser temperature high!");
1476 else if (!strncmp(reason
, "fuser-under-temp", 16))
1477 message
= _("Fuser temperature low!");
1478 else if (!strncmp(reason
, "opc-near-eol", 12))
1479 message
= _("OPC almost at end-of-life.");
1480 else if (!strncmp(reason
, "opc-life-over", 13))
1481 message
= _("OPC at end-of-life!");
1482 else if (!strncmp(reason
, "developer-low", 13))
1483 message
= _("Developer almost empty.");
1484 else if (!strncmp(reason
, "developer-empty", 15))
1485 message
= _("Developer empty!");
1486 else if (strstr(reason
, "error") != NULL
)
1490 snprintf(unknown
, sizeof(unknown
), _("Unknown printer error (%s)!"),
1497 if (strstr(reasons
->values
[i
].string
.text
, "error"))
1498 fprintf(stderr
, "ERROR: %s\n", message
);
1499 else if (strstr(reasons
->values
[i
].string
.text
, "warning"))
1500 fprintf(stderr
, "WARNING: %s\n", message
);
1502 fprintf(stderr
, "INFO: %s\n", message
);
1506 fprintf(stderr
, "%s\n", state
);
1514 * 'run_pictwps_filter()' - Convert PICT files to PostScript when printing
1517 * This step is required because the PICT format is not documented and
1518 * subject to change, so developing a filter for other OS's is infeasible.
1519 * Also, fonts required by the PICT file need to be embedded on the
1520 * client side (which has the fonts), so we run the filter to get a
1521 * PostScript file for printing...
1524 static int /* O - Exit status of filter */
1525 run_pictwps_filter(char **argv
, /* I - Command-line arguments */
1526 const char *filename
)/* I - Filename */
1528 struct stat fileinfo
; /* Print file information */
1529 const char *ppdfile
; /* PPD file for destination printer */
1530 int pid
; /* Child process ID */
1531 int fd
; /* Temporary file descriptor */
1532 int status
; /* Exit status of filter */
1533 const char *printer
; /* PRINTER env var */
1534 static char ppdenv
[1024]; /* PPD environment variable */
1538 * First get the PPD file for the printer...
1541 printer
= getenv("PRINTER");
1544 fputs(_("ERROR: PRINTER environment variable not defined!\n"), stderr
);
1548 if ((ppdfile
= cupsGetPPD(printer
)) == NULL
)
1551 _("ERROR: Unable to get PPD file for printer \"%s\" - %s.\n"),
1552 printer
, cupsLastErrorString());
1556 snprintf(ppdenv
, sizeof(ppdenv
), "PPD=%s", ppdfile
);
1561 * Then create a temporary file for printing...
1564 if ((fd
= cupsTempFd(pstmpname
, sizeof(pstmpname
))) < 0)
1566 fprintf(stderr
, _("ERROR: Unable to create temporary file - %s.\n"),
1574 * Get the owner of the spool file - it is owned by the user we want to run
1579 stat(argv
[6], &fileinfo
);
1583 * Use the OSX defaults, as an up-stream filter created the PICT
1587 fileinfo
.st_uid
= 1;
1588 fileinfo
.st_gid
= 80;
1592 chown(ppdfile
, fileinfo
.st_uid
, fileinfo
.st_gid
);
1594 fchown(fd
, fileinfo
.st_uid
, fileinfo
.st_gid
);
1597 * Finally, run the filter to convert the file...
1600 if ((pid
= fork()) == 0)
1603 * Child process for pictwpstops... Redirect output of pictwpstops to a
1614 * Change to an unpriviledged user...
1617 setgid(fileinfo
.st_gid
);
1618 setuid(fileinfo
.st_uid
);
1621 execlp("pictwpstops", printer
, argv
[1], argv
[2], argv
[3], argv
[4], argv
[5],
1623 fprintf(stderr
, _("ERROR: Unable to exec pictwpstops: %s\n"),
1636 fprintf(stderr
, _("ERROR: Unable to fork pictwpstops: %s\n"),
1645 * Now wait for the filter to complete...
1648 if (wait(&status
) < 0)
1650 fprintf(stderr
, _("ERROR: Unable to wait for pictwpstops: %s\n"),
1667 fprintf(stderr
, _("ERROR: pictwpstops exited with status %d!\n"),
1670 fprintf(stderr
, _("ERROR: pictwpstops exited on signal %d!\n"),
1678 * Return with no errors..
1683 #endif /* __APPLE__ */
1687 * 'sigterm_handler()' - Handle 'terminate' signals that stop the backend.
1691 sigterm_handler(int sig
) /* I - Signal */
1693 (void)sig
; /* remove compiler warnings... */
1698 * Flag that the job should be cancelled...
1706 * The scheduler already tried to cancel us once, now just terminate
1707 * after removing our temp files!
1711 unlink(tmpfilename
);
1716 #endif /* __APPLE__ */
1723 * End of "$Id: ipp.c 6582 2007-06-20 22:07:38Z mike $".