X-Git-Url: http://git.ipfire.org/?a=blobdiff_plain;f=backend%2Fipp.c;h=c9f7305b90ed6f696638ce330a4a13c03c8d20a1;hb=cb7f98ee7f57af247aacb94977c8f744a1d02eca;hp=dac35976673525466e142a2be0f143d325ce28bb;hpb=acb056cb175d1a003334c130e8e6bf3aa8edbff2;p=thirdparty%2Fcups.git diff --git a/backend/ipp.c b/backend/ipp.c index dac359766..c9f7305b9 100644 --- a/backend/ipp.c +++ b/backend/ipp.c @@ -1,9 +1,9 @@ /* - * "$Id: ipp.c 7948 2008-09-17 00:04:12Z mike $" + * "$Id: ipp.c 9759 2011-05-11 03:24:33Z mike $" * - * IPP backend for the Common UNIX Printing System (CUPS). + * IPP backend for CUPS. * - * Copyright 2007-2009 by Apple Inc. + * Copyright 2007-2013 by Apple Inc. * Copyright 1997-2007 by Easy Software Products, all rights reserved. * * These coded instructions, statements, and computer programs are the @@ -16,62 +16,186 @@ * * Contents: * - * main() - Send a file to the printer or server. - * cancel_job() - Cancel a print job. - * check_printer_state() - Check the printer state... - * compress_files() - Compress print files... - * password_cb() - Disable the password prompt for - * cupsDoFileRequest(). - * report_attr() - Report an IPP attribute value. + * main() - Send a file to the printer or server. + * cancel_job() - Cancel a print job. + * check_printer_state() - Check the printer state. + * monitor_printer() - Monitor the printer state. + * new_request() - Create a new print creation or validation + * request. + * password_cb() - Disable the password prompt for + * cupsDoFileRequest(). + * quote_string() - Quote a string value. + * report_attr() - Report an IPP attribute value. * report_printer_state() - Report the printer state. - * run_pictwps_filter() - Convert PICT files to PostScript when printing - * remotely. - * sigterm_handler() - Handle 'terminate' signals that stop the backend. + * run_as_user() - Run the IPP backend as the printing user. + * sigterm_handler() - Handle 'terminate' signals that stop the backend. + * timeout_cb() - Handle HTTP timeouts. + * update_reasons() - Update the printer-state-reasons values. */ /* * Include necessary headers. */ -#include #include "backend-private.h" +#include #include #include #include +#if defined(HAVE_GSSAPI) && defined(HAVE_XPC) +# include +# define kPMPrintUIToolAgent "com.apple.printuitool.agent" +# define kPMStartJob 100 +# define kPMWaitForJob 101 +# ifdef HAVE_XPC_PRIVATE_H +# include +# else +extern void xpc_connection_set_target_uid(xpc_connection_t connection, + uid_t uid); +# endif /* HAVE_XPC_PRIVATE_H */ +#endif /* HAVE_GSSAPI && HAVE_XPC */ + /* - * Globals... + * Bits for job-state-reasons we care about... */ -static char *password = NULL; /* Password for device URI */ -static int password_tries = 0; /* Password tries */ -#ifdef __APPLE__ -static char pstmpname[1024] = ""; /* Temporary PostScript file name */ -#endif /* __APPLE__ */ -static char tmpfilename[1024] = ""; /* Temporary spool file name */ -static int job_cancelled = 0; /* Job cancelled? */ +#define _CUPS_JSR_ACCOUNT_AUTHORIZATION_FAILED 0x01 +#define _CUPS_JSR_ACCOUNT_CLOSED 0x02 +#define _CUPS_JSR_ACCOUNT_INFO_NEEDED 0x04 +#define _CUPS_JSR_ACCOUNT_LIMIT_REACHED 0x08 +#define _CUPS_JSR_JOB_PASSWORD_WAIT 0x10 +#define _CUPS_JSR_JOB_RELEASE_WAIT 0x20 /* - * Local functions... + * Types... + */ + +typedef struct _cups_monitor_s /**** Monitoring data ****/ +{ + const char *uri, /* Printer URI */ + *hostname, /* Hostname */ + *user, /* Username */ + *resource; /* Resource path */ + int port, /* Port number */ + version, /* IPP version */ + job_id, /* Job ID for submitted job */ + job_reasons, /* Job state reasons bits */ + get_job_attrs; /* Support Get-Job-Attributes? */ + const char *job_name; /* Job name for submitted job */ + http_encryption_t encryption; /* Use encryption? */ + ipp_jstate_t job_state; /* Current job state */ + ipp_pstate_t printer_state; /* Current printer state */ +} _cups_monitor_t; + + +/* + * Globals... */ -static void cancel_job(http_t *http, const char *uri, int id, - const char *resource, const char *user, int version); -static void check_printer_state(http_t *http, const char *uri, - const char *resource, const char *user, - int version, int job_id); +static const char *auth_info_required; + /* New auth-info-required value */ +#if defined(HAVE_GSSAPI) && defined(HAVE_XPC) +static int child_pid = 0; /* Child process ID */ +#endif /* HAVE_GSSAPI && HAVE_XPC */ +static const char * const jattrs[] = /* Job attributes we want */ +{ + "job-id", + "job-impressions-completed", + "job-media-sheets-completed", + "job-name", + "job-originating-user-name", + "job-state", + "job-state-reasons" +}; +static int job_canceled = 0; + /* Job cancelled? */ +static char username[256] = "", + /* Username for device URI */ + *password = NULL; + /* Password for device URI */ +static int password_tries = 0; + /* Password tries */ +static const char * const pattrs[] = /* Printer attributes we want */ +{ #ifdef HAVE_LIBZ -static void compress_files(int num_files, char **files); + "compression-supported", #endif /* HAVE_LIBZ */ -static const char *password_cb(const char *); -static void report_attr(ipp_attribute_t *attr); -static int report_printer_state(ipp_t *ipp, int job_id); + "copies-supported", + "cups-version", + "document-format-supported", + "marker-colors", + "marker-high-levels", + "marker-levels", + "marker-low-levels", + "marker-message", + "marker-names", + "marker-types", + "media-col-supported", + "multiple-document-handling-supported", + "operations-supported", + "printer-alert", + "printer-alert-description", + "printer-is-accepting-jobs", + "printer-state", + "printer-state-message", + "printer-state-reasons" +}; +static const char * const remote_job_states[] = +{ /* Remote job state keywords */ + "+cups-remote-pending", + "+cups-remote-pending-held", + "+cups-remote-processing", + "+cups-remote-stopped", + "+cups-remote-canceled", + "+cups-remote-aborted", + "+cups-remote-completed" +}; +static _cups_mutex_t report_mutex = _CUPS_MUTEX_INITIALIZER; + /* Mutex to control access */ +static int num_attr_cache = 0; + /* Number of cached attributes */ +static cups_option_t *attr_cache = NULL; + /* Cached attributes */ +static cups_array_t *state_reasons; /* Array of printe-state-reasons keywords */ +static char tmpfilename[1024] = ""; + /* Temporary spool file name */ + + +/* + * Local functions... + */ -#ifdef __APPLE__ -static int run_pictwps_filter(char **argv, const char *filename); -#endif /* __APPLE__ */ -static void sigterm_handler(int sig); +static void cancel_job(http_t *http, const char *uri, int id, + const char *resource, const char *user, + int version); +static ipp_pstate_t check_printer_state(http_t *http, const char *uri, + const char *resource, + const char *user, int version); +static void *monitor_printer(_cups_monitor_t *monitor); +static ipp_t *new_request(ipp_op_t op, int version, const char *uri, + const char *user, const char *title, + int num_options, cups_option_t *options, + const char *compression, int copies, + const char *format, _ppd_cache_t *pc, + ppd_file_t *ppd, + ipp_attribute_t *media_col_sup, + ipp_attribute_t *doc_handling_sup, + int print_color_mode); +static const char *password_cb(const char *prompt, http_t *http, + const char *method, const char *resource, + void *user_data); +static const char *quote_string(const char *s, char *q, size_t qsize); +static void report_attr(ipp_attribute_t *attr); +static void report_printer_state(ipp_t *ipp); +#if defined(HAVE_GSSAPI) && defined(HAVE_XPC) +static int run_as_user(char *argv[], uid_t uid, + const char *device_uri, int fd); +#endif /* HAVE_GSSAPI && HAVE_XPC */ +static void sigterm_handler(int sig); +static int timeout_cb(http_t *http, void *user_data); +static void update_reasons(ipp_attribute_t *attr, const char *s); /* @@ -93,72 +217,77 @@ main(int argc, /* I - Number of command-line args */ const char *device_uri; /* Device URI */ char scheme[255], /* Scheme in URI */ hostname[1024], /* Hostname */ - username[255], /* Username info */ resource[1024], /* Resource info (printer name) */ addrname[256], /* Address name */ *optptr, /* Pointer to URI options */ *name, /* Name of option */ *value, /* Value of option */ sep; /* Separator character */ + http_addrlist_t *addrlist; /* Address of printer */ + int snmp_enabled = 1; /* Is SNMP enabled? */ int snmp_fd, /* SNMP socket */ start_count, /* Page count via SNMP at start */ page_count, /* Page count via SNMP */ have_supplies; /* Printer supports supply levels? */ int num_files; /* Number of files to print */ char **files, /* Files to print */ - *filename; /* Pointer to single filename */ + *compatfile = NULL; /* Compatibility filename */ + off_t compatsize = 0; /* Size of compatibility file */ int port; /* Port number (not used) */ + char portname[255]; /* Port name */ char uri[HTTP_MAX_URI]; /* Updated URI without user/pass */ + char print_job_name[1024]; /* Update job-name for Print-Job */ + http_status_t http_status; /* Status of HTTP request */ ipp_status_t ipp_status; /* Status of IPP request */ http_t *http; /* HTTP connection */ ipp_t *request, /* IPP request */ *response, /* IPP response */ *supported; /* get-printer-attributes response */ time_t start_time; /* Time of first connect */ - int recoverable; /* Recoverable error shown? */ int contimeout; /* Connection timeout */ - int delay; /* Delay for retries... */ - int compression, /* Do compression of the job data? */ - waitjob, /* Wait for job complete? */ + int delay, /* Delay for retries */ + prev_delay; /* Previous delay */ + const char *compression; /* Compression mode */ + int waitjob, /* Wait for job complete? */ + waitjob_tries = 0, /* Number of times we've waited */ waitprinter; /* Wait for printer ready? */ + _cups_monitor_t monitor; /* Monitoring data */ ipp_attribute_t *job_id_attr; /* job-id attribute */ int job_id; /* job-id value */ ipp_attribute_t *job_sheets; /* job-media-sheets-completed */ ipp_attribute_t *job_state; /* job-state */ +#ifdef HAVE_LIBZ + ipp_attribute_t *compression_sup; /* compression-supported */ +#endif /* HAVE_LIBZ */ ipp_attribute_t *copies_sup; /* copies-supported */ + ipp_attribute_t *cups_version; /* cups-version */ ipp_attribute_t *format_sup; /* document-format-supported */ + ipp_attribute_t *job_auth; /* job-authorization-uri */ + ipp_attribute_t *media_col_sup; /* media-col-supported */ + ipp_attribute_t *operations_sup; /* operations-supported */ + ipp_attribute_t *doc_handling_sup; /* multiple-document-handling-supported */ ipp_attribute_t *printer_state; /* printer-state attribute */ ipp_attribute_t *printer_accepting; /* printer-is-accepting-jobs */ + int create_job = 0, /* Does printer support Create-Job? */ + get_job_attrs = 0, /* Does printer support Get-Job-Attributes? */ + send_document = 0, /* Does printer support Send-Document? */ + validate_job = 0, /* Does printer support Validate-Job? */ + print_color_mode = 0; /* Does printer support print-color-mode? */ int copies, /* Number of copies for job */ copies_remaining; /* Number of copies remaining */ const char *content_type, /* CONTENT_TYPE environment variable */ - *final_content_type; /* FINAL_CONTENT_TYPE environment var */ + *final_content_type, /* FINAL_CONTENT_TYPE environment var */ + *document_format; /* document-format value */ + int fd; /* File descriptor */ + off_t bytes = 0; /* Bytes copied */ + char buffer[16384]; /* Copy buffer */ #if defined(HAVE_SIGACTION) && !defined(HAVE_SIGSET) struct sigaction action; /* Actions for POSIX signals */ #endif /* HAVE_SIGACTION && !HAVE_SIGSET */ int version; /* IPP version */ - static const char * const pattrs[] = - { /* Printer attributes we want */ - "com.apple.print.recoverable-message", - "copies-supported", - "document-format-supported", - "marker-colors", - "marker-high-levels", - "marker-levels", - "marker-low-levels", - "marker-message", - "marker-names", - "marker-types", - "printer-is-accepting-jobs", - "printer-state", - "printer-state-message", - "printer-state-reasons", - }; - static const char * const jattrs[] = - { /* Job attributes we want */ - "job-media-sheets-completed", - "job-state" - }; + ppd_file_t *ppd = NULL; /* PPD file */ + _ppd_cache_t *pc = NULL; /* PPD cache and mapping data */ + fd_set input; /* Input set for select() */ /* @@ -209,11 +338,74 @@ main(int argc, /* I - Number of command-line args */ else if (argc < 6) { _cupsLangPrintf(stderr, - _("Usage: %s job-id user title copies options [file]\n"), + _("Usage: %s job-id user title copies options [file]"), argv[0]); return (CUPS_BACKEND_STOP); } + /* + * Get the device URI... + */ + + while ((device_uri = cupsBackendDeviceURI(argv)) == NULL) + { + _cupsLangPrintFilter(stderr, "INFO", _("Unable to locate printer.")); + sleep(10); + + if (getenv("CLASS") != NULL) + return (CUPS_BACKEND_FAILED); + } + + if ((auth_info_required = getenv("AUTH_INFO_REQUIRED")) == NULL) + auth_info_required = "none"; + + state_reasons = _cupsArrayNewStrings(getenv("PRINTER_STATE_REASONS"), ','); + +#ifdef HAVE_GSSAPI + /* + * For Kerberos, become the printing user (if we can) to get the credentials + * that way. + */ + + if (!getuid() && (value = getenv("AUTH_UID")) != NULL) + { + uid_t uid = (uid_t)atoi(value); + /* User ID */ + +# ifdef HAVE_XPC + if (uid > 0) + { + if (argc == 6) + return (run_as_user(argv, uid, device_uri, 0)); + else + { + int status = 0; /* Exit status */ + + for (i = 6; i < argc && !status && !job_canceled; i ++) + { + if ((fd = open(argv[i], O_RDONLY)) >= 0) + { + status = run_as_user(argv, uid, device_uri, fd); + close(fd); + } + else + { + _cupsLangPrintError("ERROR", _("Unable to open print file")); + status = CUPS_BACKEND_FAILED; + } + } + + return (status); + } + } + +# else /* No XPC, just try to run as the user ID */ + if (uid > 0) + seteuid(uid); +# endif /* HAVE_XPC */ + } +#endif /* HAVE_GSSAPI */ + /* * Get the (final) content type... */ @@ -233,9 +425,6 @@ main(int argc, /* I - Number of command-line args */ * Extract the hostname and printer name from the URI... */ - if ((device_uri = cupsBackendDeviceURI(argv)) == NULL) - return (CUPS_BACKEND_FAILED); - httpSeparateURI(HTTP_URI_CODING_ALL, device_uri, scheme, sizeof(scheme), username, sizeof(username), hostname, sizeof(hostname), &port, resource, sizeof(resource)); @@ -252,8 +441,8 @@ main(int argc, /* I - Number of command-line args */ * See if there are any options... */ - compression = 0; - version = 11; + compression = NULL; + version = 20; waitjob = 1; waitprinter = 1; contimeout = 7 * 24 * 60 * 60; @@ -306,48 +495,58 @@ main(int argc, /* I - Number of command-line args */ * Process the option... */ - if (!strcasecmp(name, "waitjob")) + if (!_cups_strcasecmp(name, "waitjob")) { /* * Wait for job completion? */ - waitjob = !strcasecmp(value, "on") || - !strcasecmp(value, "yes") || - !strcasecmp(value, "true"); + waitjob = !_cups_strcasecmp(value, "on") || + !_cups_strcasecmp(value, "yes") || + !_cups_strcasecmp(value, "true"); } - else if (!strcasecmp(name, "waitprinter")) + else if (!_cups_strcasecmp(name, "waitprinter")) { /* * Wait for printer idle? */ - waitprinter = !strcasecmp(value, "on") || - !strcasecmp(value, "yes") || - !strcasecmp(value, "true"); + waitprinter = !_cups_strcasecmp(value, "on") || + !_cups_strcasecmp(value, "yes") || + !_cups_strcasecmp(value, "true"); } - else if (!strcasecmp(name, "encryption")) + else if (!_cups_strcasecmp(name, "encryption")) { /* * Enable/disable encryption? */ - if (!strcasecmp(value, "always")) + if (!_cups_strcasecmp(value, "always")) cupsSetEncryption(HTTP_ENCRYPT_ALWAYS); - else if (!strcasecmp(value, "required")) + else if (!_cups_strcasecmp(value, "required")) cupsSetEncryption(HTTP_ENCRYPT_REQUIRED); - else if (!strcasecmp(value, "never")) + else if (!_cups_strcasecmp(value, "never")) cupsSetEncryption(HTTP_ENCRYPT_NEVER); - else if (!strcasecmp(value, "ifrequested")) + else if (!_cups_strcasecmp(value, "ifrequested")) cupsSetEncryption(HTTP_ENCRYPT_IF_REQUESTED); else { - _cupsLangPrintf(stderr, - _("ERROR: Unknown encryption option value \"%s\"!\n"), - value); + _cupsLangPrintFilter(stderr, "ERROR", + _("Unknown encryption option value: \"%s\"."), + value); } } - else if (!strcasecmp(name, "version")) + else if (!_cups_strcasecmp(name, "snmp")) + { + /* + * Enable/disable SNMP stuff... + */ + + snmp_enabled = !value[0] || !_cups_strcasecmp(value, "on") || + _cups_strcasecmp(value, "yes") || + _cups_strcasecmp(value, "true"); + } + else if (!_cups_strcasecmp(name, "version")) { if (!strcmp(value, "1.0")) version = 10; @@ -357,23 +556,31 @@ main(int argc, /* I - Number of command-line args */ version = 20; else if (!strcmp(value, "2.1")) version = 21; + else if (!strcmp(value, "2.2")) + version = 22; else { - _cupsLangPrintf(stderr, - _("ERROR: Unknown version option value \"%s\"!\n"), - value); + _cupsLangPrintFilter(stderr, "ERROR", + _("Unknown version option value: \"%s\"."), + value); } } #ifdef HAVE_LIBZ - else if (!strcasecmp(name, "compression")) + else if (!_cups_strcasecmp(name, "compression")) { - compression = !strcasecmp(value, "true") || - !strcasecmp(value, "yes") || - !strcasecmp(value, "on") || - !strcasecmp(value, "gzip"); + if (!_cups_strcasecmp(value, "true") || !_cups_strcasecmp(value, "yes") || + !_cups_strcasecmp(value, "on") || !_cups_strcasecmp(value, "gzip")) + compression = "gzip"; + else if (!_cups_strcasecmp(value, "deflate")) + compression = "deflate"; + else if (!_cups_strcasecmp(value, "false") || + !_cups_strcasecmp(value, "no") || + !_cups_strcasecmp(value, "off") || + !_cups_strcasecmp(value, "none")) + compression = "none"; } #endif /* HAVE_LIBZ */ - else if (!strcasecmp(name, "contimeout")) + else if (!_cups_strcasecmp(name, "contimeout")) { /* * Set the connection timeout... @@ -388,9 +595,9 @@ main(int argc, /* I - Number of command-line args */ * Unknown option... */ - _cupsLangPrintf(stderr, - _("ERROR: Unknown option \"%s\" with value \"%s\"!\n"), - name, value); + _cupsLangPrintFilter(stderr, "ERROR", + _("Unknown option \"%s\" with value \"%s\"."), + name, value); } } } @@ -403,81 +610,13 @@ main(int argc, /* I - Number of command-line args */ if (argc == 6) { - /* - * Copy stdin to a temporary file... - */ - - int fd; /* File descriptor */ - http_addrlist_t *addrlist; /* Address list */ - char buffer[8192]; /* Buffer for copying */ - int bytes; /* Number of bytes read */ - off_t tbytes; /* Total bytes copied */ - - - fputs("STATE: +connecting-to-device\n", stderr); - fprintf(stderr, "DEBUG: Looking up \"%s\"...\n", hostname); - - if ((addrlist = httpAddrGetList(hostname, AF_UNSPEC, "1")) == NULL) - { - _cupsLangPrintf(stderr, _("ERROR: Unable to locate printer \'%s\'!\n"), - hostname); - return (CUPS_BACKEND_STOP); - } - - snmp_fd = _cupsSNMPOpen(addrlist->addr.addr.sa_family); - - if ((fd = cupsTempFd(tmpfilename, sizeof(tmpfilename))) < 0) - { - _cupsLangPrintError(_("ERROR: Unable to create temporary file")); - return (CUPS_BACKEND_FAILED); - } - - _cupsLangPuts(stderr, _("INFO: Copying print data...\n")); - - tbytes = 0; - - while ((bytes = fread(buffer, 1, sizeof(buffer), stdin)) > 0) - { - if (write(fd, buffer, bytes) < bytes) - { - _cupsLangPrintError(_("ERROR: Unable to write to temporary file")); - close(fd); - unlink(tmpfilename); - return (CUPS_BACKEND_FAILED); - } - else - tbytes += bytes; - - if (snmp_fd >= 0) - backendCheckSideChannel(snmp_fd, &(addrlist->addr)); - } - - if (snmp_fd >= 0) - _cupsSNMPClose(snmp_fd); - - httpAddrFreeList(addrlist); - - close(fd); - - /* - * Don't try printing files less than 2 bytes... - */ + num_files = 0; + files = NULL; + send_options = !_cups_strcasecmp(final_content_type, "application/pdf") || + !_cups_strcasecmp(final_content_type, "application/vnd.cups-pdf") || + !_cups_strncasecmp(final_content_type, "image/", 6); - if (tbytes <= 1) - { - _cupsLangPuts(stderr, _("ERROR: Empty print file!\n")); - unlink(tmpfilename); - return (CUPS_BACKEND_FAILED); - } - - /* - * Point to the single file from stdin... - */ - - filename = tmpfilename; - num_files = 1; - files = &filename; - send_options = 0; + fputs("DEBUG: Sending stdin for job...\n", stderr); } else { @@ -489,24 +628,19 @@ main(int argc, /* I - Number of command-line args */ files = argv + 6; send_options = 1; -#ifdef HAVE_LIBZ - if (compression) - compress_files(num_files, files); -#endif /* HAVE_LIBZ */ + fprintf(stderr, "DEBUG: %d files to send in job...\n", num_files); } - fprintf(stderr, "DEBUG: %d files to send in job...\n", num_files); - /* * Set the authentication info, if any... */ - cupsSetPasswordCB(password_cb); + cupsSetPasswordCB2(password_cb, NULL); if (username[0]) { /* - * Use authenticaion information in the device URI... + * Use authentication information in the device URI... */ if ((password = strchr(username, ':')) != NULL) @@ -514,7 +648,7 @@ main(int argc, /* I - Number of command-line args */ cupsSetUser(username); } - else if (!getuid()) + else { /* * Try loading authentication information from the environment. @@ -523,29 +657,91 @@ main(int argc, /* I - Number of command-line args */ const char *ptr = getenv("AUTH_USERNAME"); if (ptr) + { + strlcpy(username, ptr, sizeof(username)); cupsSetUser(ptr); + } password = getenv("AUTH_PASSWORD"); } /* - * Try connecting to the remote server... + * Try finding the remote server... + */ + + start_time = time(NULL); + + sprintf(portname, "%d", port); + + update_reasons(NULL, "+connecting-to-device"); + fprintf(stderr, "DEBUG: Looking up \"%s\"...\n", hostname); + + while ((addrlist = httpAddrGetList(hostname, AF_UNSPEC, portname)) == NULL) + { + _cupsLangPrintFilter(stderr, "INFO", + _("Unable to locate printer \"%s\"."), hostname); + sleep(10); + + if (getenv("CLASS") != NULL) + { + update_reasons(NULL, "-connecting-to-device"); + return (CUPS_BACKEND_STOP); + } + + if (job_canceled) + return (CUPS_BACKEND_OK); + } + + http = httpConnect2(hostname, port, addrlist, AF_UNSPEC, cupsEncryption(), 1, + 0, NULL); + httpSetTimeout(http, 30.0, timeout_cb, NULL); + + /* + * See if the printer supports SNMP... + */ + + if (snmp_enabled) + snmp_fd = _cupsSNMPOpen(addrlist->addr.addr.sa_family); + else + snmp_fd = -1; + + if (snmp_fd >= 0) + have_supplies = !backendSNMPSupplies(snmp_fd, &(addrlist->addr), + &start_count, NULL); + else + have_supplies = start_count = 0; + + /* + * Wait for data from the filter... */ - delay = 5; - recoverable = 0; - start_time = time(NULL); + if (num_files == 0) + { + if (!backendWaitLoop(snmp_fd, &(addrlist->addr), 0, backendNetworkSideCB)) + return (CUPS_BACKEND_OK); + else if ((bytes = read(0, buffer, sizeof(buffer))) <= 0) + return (CUPS_BACKEND_OK); + } + + /* + * Try connecting to the remote server... + */ - fputs("STATE: +connecting-to-device\n", stderr); + delay = _cupsNextDelay(0, &prev_delay); do { fprintf(stderr, "DEBUG: Connecting to %s:%d\n", hostname, port); - _cupsLangPuts(stderr, _("INFO: Connecting to printer...\n")); + _cupsLangPrintFilter(stderr, "INFO", _("Connecting to printer.")); - if ((http = httpConnectEncrypt(hostname, port, cupsEncryption())) == NULL) + if (httpReconnect(http)) { - if (job_cancelled) + int error = errno; /* Connection error */ + + if (http->status == HTTP_PKI_ERROR) + update_reasons(NULL, "+cups-certificate-error"); + + if (job_canceled) break; if (getenv("CLASS") != NULL) @@ -557,12 +753,9 @@ main(int argc, /* I - Number of command-line args */ * available printer in the class. */ - _cupsLangPuts(stderr, - _("INFO: Unable to contact printer, queuing on next " - "printer in class...\n")); - - if (tmpfilename[0]) - unlink(tmpfilename); + _cupsLangPrintFilter(stderr, "INFO", + _("Unable to contact printer, queuing on next " + "printer in class.")); /* * Sleep 5 seconds to keep the job from requeuing too rapidly... @@ -570,85 +763,75 @@ main(int argc, /* I - Number of command-line args */ sleep(5); + update_reasons(NULL, "-connecting-to-device"); + return (CUPS_BACKEND_FAILED); } + fprintf(stderr, "DEBUG: Connection error: %s\n", strerror(errno)); + if (errno == ECONNREFUSED || errno == EHOSTDOWN || errno == EHOSTUNREACH) { if (contimeout && (time(NULL) - start_time) > contimeout) { - _cupsLangPuts(stderr, _("ERROR: Printer not responding!\n")); + _cupsLangPrintFilter(stderr, "ERROR", + _("The printer is not responding.")); + update_reasons(NULL, "-connecting-to-device"); return (CUPS_BACKEND_FAILED); } - recoverable = 1; - - _cupsLangPrintf(stderr, - _("WARNING: recoverable: Network host \'%s\' is busy; " - "will retry in %d seconds...\n"), - hostname, delay); + switch (error) + { + case EHOSTDOWN : + _cupsLangPrintFilter(stderr, "WARNING", + _("The printer may not exist or " + "is unavailable at this time.")); + break; + + case EHOSTUNREACH : + _cupsLangPrintFilter(stderr, "WARNING", + _("The printer is unreachable at this " + "time.")); + break; + + case ECONNREFUSED : + default : + _cupsLangPrintFilter(stderr, "WARNING", + _("The printer is in use.")); + break; + } sleep(delay); - if (delay < 30) - delay += 5; - } - else if (h_errno) - { - _cupsLangPrintf(stderr, _("ERROR: Unable to locate printer \'%s\'!\n"), - hostname); - return (CUPS_BACKEND_STOP); + delay = _cupsNextDelay(delay, &prev_delay); } else { - recoverable = 1; - - fprintf(stderr, "DEBUG: Connection error: %s\n", strerror(errno)); - _cupsLangPuts(stderr, - _("ERROR: recoverable: Unable to connect to printer; will " - "retry in 30 seconds...\n")); + _cupsLangPrintFilter(stderr, "ERROR", + _("The printer is not responding.")); sleep(30); } - if (job_cancelled) + if (job_canceled) break; } + else + update_reasons(NULL, "-cups-certificate-error"); } - while (http == NULL); - - if (job_cancelled || !http) - { - if (tmpfilename[0]) - unlink(tmpfilename); + while (http->fd < 0); + if (job_canceled) + return (CUPS_BACKEND_OK); + else if (!http) return (CUPS_BACKEND_FAILED); - } - - fputs("STATE: -connecting-to-device\n", stderr); - _cupsLangPuts(stderr, _("INFO: Connected to printer...\n")); - -#ifdef AF_INET6 - if (http->hostaddr->addr.sa_family == AF_INET6) - fprintf(stderr, "DEBUG: Connected to [%s]:%d (IPv6)...\n", - httpAddrString(http->hostaddr, addrname, sizeof(addrname)), - ntohs(http->hostaddr->ipv6.sin6_port)); - else -#endif /* AF_INET6 */ - if (http->hostaddr->addr.sa_family == AF_INET) - fprintf(stderr, "DEBUG: Connected to %s:%d (IPv4)...\n", - httpAddrString(http->hostaddr, addrname, sizeof(addrname)), - ntohs(http->hostaddr->ipv4.sin_port)); - /* - * See if the printer supports SNMP... - */ + update_reasons(NULL, "-connecting-to-device"); + _cupsLangPrintFilter(stderr, "INFO", _("Connected to printer.")); - if ((snmp_fd = _cupsSNMPOpen(http->hostaddr->addr.sa_family)) >= 0) - have_supplies = !backendSNMPSupplies(snmp_fd, http->hostaddr, &start_count, - NULL); - else - have_supplies = start_count = 0; + fprintf(stderr, "DEBUG: Connected to %s:%d...\n", + httpAddrString(http->hostaddr, addrname, sizeof(addrname)), + httpAddrPort(http->hostaddr)); /* * Build a URI for the printer and fill the standard IPP attributes for @@ -661,13 +844,19 @@ main(int argc, /* I - Number of command-line args */ /* * First validate the destination and see if the device supports multiple - * copies. We have to do this because some IPP servers (e.g. HP JetDirect) - * don't support the copies attribute... + * copies... */ - copies_sup = NULL; - format_sup = NULL; - supported = NULL; +#ifdef HAVE_LIBZ + compression_sup = NULL; +#endif /* HAVE_LIBZ */ + copies_sup = NULL; + cups_version = NULL; + format_sup = NULL; + media_col_sup = NULL; + supported = NULL; + operations_sup = NULL; + doc_handling_sup = NULL; do { @@ -699,89 +888,217 @@ main(int argc, /* I - Number of command-line args */ fputs("DEBUG: Getting supported attributes...\n", stderr); if (http->version < HTTP_1_1) - httpReconnect(http); + { + fprintf(stderr, "DEBUG: Printer responded with HTTP version %d.%d.\n", + http->version / 100, http->version % 100); + update_reasons(NULL, "+cups-ipp-conformance-failure-report," + "cups-ipp-wrong-http-version"); + } - if ((supported = cupsDoRequest(http, request, resource)) == NULL) - ipp_status = cupsLastError(); - else - ipp_status = supported->request.status.status_code; + supported = cupsDoRequest(http, request, resource); + ipp_status = cupsLastError(); - if (ipp_status > IPP_OK_CONFLICT) + fprintf(stderr, "DEBUG: Get-Printer-Attributes: %s (%s)\n", + ippErrorString(ipp_status), cupsLastErrorString()); + + if (ipp_status <= IPP_OK_CONFLICT) + password_tries = 0; + else { + fprintf(stderr, "DEBUG: Get-Printer-Attributes returned %s.\n", + ippErrorString(ipp_status)); + if (ipp_status == IPP_PRINTER_BUSY || ipp_status == IPP_SERVICE_UNAVAILABLE) { if (contimeout && (time(NULL) - start_time) > contimeout) { - _cupsLangPuts(stderr, _("ERROR: Printer not responding!\n")); + _cupsLangPrintFilter(stderr, "ERROR", + _("The printer is not responding.")); return (CUPS_BACKEND_FAILED); } - recoverable = 1; + _cupsLangPrintFilter(stderr, "INFO", _("The printer is in use.")); - _cupsLangPrintf(stderr, - _("WARNING: recoverable: Network host \'%s\' is busy; " - "will retry in %d seconds...\n"), - hostname, delay); - - report_printer_state(supported, 0); + report_printer_state(supported); sleep(delay); - if (delay < 30) - delay += 5; + delay = _cupsNextDelay(delay, &prev_delay); } else if ((ipp_status == IPP_BAD_REQUEST || ipp_status == IPP_VERSION_NOT_SUPPORTED) && version > 10) { /* - * Switch to IPP/1.0... + * Switch to IPP/1.1 or IPP/1.0... */ - _cupsLangPrintf(stderr, - _("INFO: Printer does not support IPP/%d.%d, trying " - "IPP/1.0...\n"), version / 10, version % 10); - version = 10; + if (version >= 20) + { + _cupsLangPrintFilter(stderr, "INFO", _("Preparing to print.")); + fprintf(stderr, + "DEBUG: The printer does not support IPP/%d.%d, trying " + "IPP/1.1.\n", version / 10, version % 10); + version = 11; + } + else + { + _cupsLangPrintFilter(stderr, "INFO", _("Preparing to print.")); + fprintf(stderr, + "DEBUG: The printer does not support IPP/%d.%d, trying " + "IPP/1.0.\n", version / 10, version % 10); + version = 10; + } + httpReconnect(http); } else if (ipp_status == IPP_NOT_FOUND) { - _cupsLangPuts(stderr, _("ERROR: Destination printer does not exist!\n")); + _cupsLangPrintFilter(stderr, "ERROR", + _("The printer configuration is incorrect or the " + "printer no longer exists.")); - if (supported) - ippDelete(supported); + ippDelete(supported); return (CUPS_BACKEND_STOP); } - else + else if (ipp_status == IPP_FORBIDDEN || + ipp_status == IPP_AUTHENTICATION_CANCELED) { - _cupsLangPrintf(stderr, - _("ERROR: Unable to get printer status (%s)!\n"), - cupsLastErrorString()); - sleep(10); + const char *www_auth = httpGetField(http, HTTP_FIELD_WWW_AUTHENTICATE); + /* WWW-Authenticate field value */ + + if (!strncmp(www_auth, "Negotiate", 9)) + auth_info_required = "negotiate"; + else if (www_auth[0]) + auth_info_required = "username,password"; + + fprintf(stderr, "ATTR: auth-info-required=%s\n", auth_info_required); + return (CUPS_BACKEND_AUTH_REQUIRED); } + else if (ipp_status != IPP_NOT_AUTHORIZED) + { + _cupsLangPrintFilter(stderr, "ERROR", + _("Unable to get printer status.")); + sleep(10); - if (supported) - ippDelete(supported); + httpReconnect(http); + } + ippDelete(supported); + supported = NULL; continue; } - else if ((copies_sup = ippFindAttribute(supported, "copies-supported", - IPP_TAG_RANGE)) != NULL) + + if (!getenv("CLASS")) + { + /* + * Check printer-is-accepting-jobs = false and printer-state-reasons for the + * "spool-area-full" keyword... + */ + + int busy = 0; + + if ((printer_accepting = ippFindAttribute(supported, + "printer-is-accepting-jobs", + IPP_TAG_BOOLEAN)) != NULL && + !printer_accepting->values[0].boolean) + busy = 1; + else if (!printer_accepting) + update_reasons(NULL, "+cups-ipp-conformance-failure-report," + "cups-ipp-missing-printer-is-accepting-jobs"); + + if ((printer_state = ippFindAttribute(supported, + "printer-state-reasons", + IPP_TAG_KEYWORD)) == NULL) + { + update_reasons(NULL, "+cups-ipp-conformance-failure-report," + "cups-ipp-missing-printer-state-reasons"); + } + else if (!busy) + { + for (i = 0; i < printer_state->num_values; i ++) + { + if (!strcmp(printer_state->values[0].string.text, + "spool-area-full") || + !strncmp(printer_state->values[0].string.text, "spool-area-full-", + 16)) + { + busy = 1; + break; + } + } + } + + if (busy) + { + _cupsLangPrintFilter(stderr, "INFO", _("The printer is in use.")); + + report_printer_state(supported); + + sleep(delay); + + delay = _cupsNextDelay(delay, &prev_delay); + + ippDelete(supported); + supported = NULL; + continue; + } + } + + /* + * Check for supported attributes... + */ + +#ifdef HAVE_LIBZ + if ((compression_sup = ippFindAttribute(supported, "compression-supported", + IPP_TAG_KEYWORD)) != NULL) + { + /* + * Check whether the requested compression is supported and/or default to + * compression if supported... + */ + + if (compression && !ippContainsString(compression_sup, compression)) + { + fprintf(stderr, "DEBUG: Printer does not support the requested " + "compression value \"%s\".\n", compression); + compression = NULL; + } + else if (!compression) + { + if (ippContainsString(compression_sup, "gzip")) + compression = "gzip"; + else if (ippContainsString(compression_sup, "deflate")) + compression = "deflate"; + + if (compression) + fprintf(stderr, "DEBUG: Automatically using \"%s\" compression.\n", + compression); + } + } +#endif /* HAVE_LIBZ */ + + if ((copies_sup = ippFindAttribute(supported, "copies-supported", + IPP_TAG_RANGE)) != NULL) { /* * Has the "copies-supported" attribute - does it have an upper * bound > 1? */ + fprintf(stderr, "DEBUG: copies-supported=%d-%d\n", + copies_sup->values[0].range.lower, + copies_sup->values[0].range.upper); + if (copies_sup->values[0].range.upper <= 1) copies_sup = NULL; /* No */ } - format_sup = ippFindAttribute(supported, "document-format-supported", - IPP_TAG_MIMETYPE); + cups_version = ippFindAttribute(supported, "cups-version", IPP_TAG_TEXT); - if (format_sup) + if ((format_sup = ippFindAttribute(supported, "document-format-supported", + IPP_TAG_MIMETYPE)) != NULL) { fprintf(stderr, "DEBUG: document-format-supported (%d values)\n", format_sup->num_values); @@ -790,45 +1107,134 @@ main(int argc, /* I - Number of command-line args */ format_sup->values[i].string.text); } - report_printer_state(supported, 0); - } - while (ipp_status > IPP_OK_CONFLICT); - - /* - * See if the printer is accepting jobs and is not stopped; if either - * condition is true and we are printing to a class, requeue the job... - */ + if ((media_col_sup = ippFindAttribute(supported, "media-col-supported", + IPP_TAG_KEYWORD)) != NULL) + { + fprintf(stderr, "DEBUG: media-col-supported (%d values)\n", + media_col_sup->num_values); + for (i = 0; i < media_col_sup->num_values; i ++) + fprintf(stderr, "DEBUG: [%d] = \"%s\"\n", i, + media_col_sup->values[i].string.text); + } - if (getenv("CLASS") != NULL) - { - printer_state = ippFindAttribute(supported, "printer-state", - IPP_TAG_ENUM); - printer_accepting = ippFindAttribute(supported, "printer-is-accepting-jobs", - IPP_TAG_BOOLEAN); + print_color_mode = ippFindAttribute(supported, + "print-color-mode-supported", + IPP_TAG_KEYWORD) != NULL; - if (printer_state == NULL || - (printer_state->values[0].integer > IPP_PRINTER_PROCESSING && - waitprinter) || - printer_accepting == NULL || - !printer_accepting->values[0].boolean) + if ((operations_sup = ippFindAttribute(supported, "operations-supported", + IPP_TAG_ENUM)) != NULL) { - /* - * If the CLASS environment variable is set, the job was submitted - * to a class and not to a specific queue. In this case, we want - * to abort immediately so that the job can be requeued on the next - * available printer in the class. - */ + fprintf(stderr, "DEBUG: operations-supported (%d values)\n", + operations_sup->num_values); + for (i = 0; i < operations_sup->num_values; i ++) + fprintf(stderr, "DEBUG: [%d] = %s\n", i, + ippOpString(operations_sup->values[i].integer)); + + for (i = 0; i < operations_sup->num_values; i ++) + if (operations_sup->values[i].integer == IPP_PRINT_JOB) + break; + + if (i >= operations_sup->num_values) + update_reasons(NULL, "+cups-ipp-conformance-failure-report," + "cups-ipp-missing-print-job"); + + for (i = 0; i < operations_sup->num_values; i ++) + if (operations_sup->values[i].integer == IPP_CANCEL_JOB) + break; + + if (i >= operations_sup->num_values) + update_reasons(NULL, "+cups-ipp-conformance-failure-report," + "cups-ipp-missing-cancel-job"); + + for (i = 0; i < operations_sup->num_values; i ++) + if (operations_sup->values[i].integer == IPP_GET_JOB_ATTRIBUTES) + break; + + if (i >= operations_sup->num_values) + update_reasons(NULL, "+cups-ipp-conformance-failure-report," + "cups-ipp-missing-get-job-attributes"); + + for (i = 0; i < operations_sup->num_values; i ++) + if (operations_sup->values[i].integer == IPP_GET_PRINTER_ATTRIBUTES) + break; + + if (i >= operations_sup->num_values) + update_reasons(NULL, "+cups-ipp-conformance-failure-report," + "cups-ipp-missing-get-printer-attributes"); + + for (i = 0; i < operations_sup->num_values; i ++) + { + if (operations_sup->values[i].integer == IPP_VALIDATE_JOB) + validate_job = 1; + else if (operations_sup->values[i].integer == IPP_CREATE_JOB) + create_job = 1; + else if (operations_sup->values[i].integer == IPP_SEND_DOCUMENT) + send_document = 1; + else if (operations_sup->values[i].integer == IPP_GET_JOB_ATTRIBUTES) + get_job_attrs = 1; + } + + if (create_job && !send_document) + { + fputs("DEBUG: Printer supports Create-Job but not Send-Document.\n", + stderr); + create_job = 0; + + update_reasons(NULL, "+cups-ipp-conformance-failure-report," + "cups-ipp-missing-send-document"); + } + + if (!validate_job) + update_reasons(NULL, "+cups-ipp-conformance-failure-report," + "cups-ipp-missing-validate-job"); + } + else + update_reasons(NULL, "+cups-ipp-conformance-failure-report," + "cups-ipp-missing-operations-supported"); + + doc_handling_sup = ippFindAttribute(supported, + "multiple-document-handling-supported", + IPP_TAG_KEYWORD); + + report_printer_state(supported); + } + while (!job_canceled && ipp_status > IPP_OK_CONFLICT); + + if (job_canceled) + return (CUPS_BACKEND_OK); + + /* + * See if the printer is accepting jobs and is not stopped; if either + * condition is true and we are printing to a class, requeue the job... + */ + + if (getenv("CLASS") != NULL) + { + printer_state = ippFindAttribute(supported, "printer-state", + IPP_TAG_ENUM); + printer_accepting = ippFindAttribute(supported, "printer-is-accepting-jobs", + IPP_TAG_BOOLEAN); + + if (printer_state == NULL || + (printer_state->values[0].integer > IPP_PRINTER_PROCESSING && + waitprinter) || + printer_accepting == NULL || + !printer_accepting->values[0].boolean) + { + /* + * If the CLASS environment variable is set, the job was submitted + * to a class and not to a specific queue. In this case, we want + * to abort immediately so that the job can be requeued on the next + * available printer in the class. + */ - _cupsLangPuts(stderr, - _("INFO: Unable to contact printer, queuing on next " - "printer in class...\n")); + _cupsLangPrintFilter(stderr, "INFO", + _("Unable to contact printer, queuing on next " + "printer in class.")); ippDelete(supported); httpClose(http); - if (tmpfilename[0]) - unlink(tmpfilename); - /* * Sleep 5 seconds to keep the job from requeuing too rapidly... */ @@ -839,18 +1245,6 @@ main(int argc, /* I - Number of command-line args */ } } - if (recoverable) - { - /* - * If we've shown a recoverable error make sure the printer proxies - * have a chance to see the recovered message. Not pretty but - * necessary for now... - */ - - fputs("INFO: recovered: \n", stderr); - sleep(5); - } - /* * See if the printer supports multiple copies... */ @@ -858,249 +1252,427 @@ main(int argc, /* I - Number of command-line args */ copies = atoi(argv[4]); if (copies_sup || argc < 7) - { copies_remaining = 1; - - if (argc < 7) - copies = 1; - } else copies_remaining = copies; /* - * Then issue the print-job request... + * Prepare remaining printing options... */ - job_id = 0; + options = NULL; - while (copies_remaining > 0) + if (send_options) { - /* - * Check for side-channel requests... - */ + num_options = cupsParseOptions(argv[5], 0, &options); - backendCheckSideChannel(snmp_fd, http->hostaddr); + if (!cups_version && media_col_sup) + { + /* + * Load the PPD file and generate PWG attribute mapping information... + */ - /* - * Build the IPP request... - */ + ppd = ppdOpenFile(getenv("PPD")); + pc = _ppdCacheCreateWithPPD(ppd); - if (job_cancelled) - break; + ppdMarkDefaults(ppd); + cupsMarkOptions(ppd, num_options, options); + } + } + else + num_options = 0; - if (num_files > 1) - request = ippNewRequest(IPP_CREATE_JOB); - else - request = ippNewRequest(IPP_PRINT_JOB); + document_format = NULL; - request->request.op.version[0] = version / 10; - request->request.op.version[1] = version % 10; + if (format_sup != NULL) + { + for (i = 0; i < format_sup->num_values; i ++) + if (!_cups_strcasecmp(final_content_type, + format_sup->values[i].string.text)) + { + document_format = final_content_type; + break; + } - ippAddString(request, IPP_TAG_OPERATION, IPP_TAG_URI, "printer-uri", - NULL, uri); + if (!document_format) + { + for (i = 0; i < format_sup->num_values; i ++) + if (!_cups_strcasecmp("application/octet-stream", + format_sup->values[i].string.text)) + { + document_format = "application/octet-stream"; + break; + } + } + } - fprintf(stderr, "DEBUG: printer-uri = \"%s\"\n", uri); + fprintf(stderr, "DEBUG: final_content_type=\"%s\", document_format=\"%s\"\n", + final_content_type, document_format ? document_format : "(null)"); - if (argv[2][0]) - ippAddString(request, IPP_TAG_OPERATION, IPP_TAG_NAME, - "requesting-user-name", NULL, argv[2]); + /* + * If the printer does not support HTTP/1.1 (which IPP requires), copy stdin + * to a temporary file so that we can do a HTTP/1.0 submission... + * + * (I hate compatibility hacks!) + */ - fprintf(stderr, "DEBUG: requesting-user-name = \"%s\"\n", argv[2]); + if (http->version < HTTP_1_1 && num_files == 0) + { + if ((fd = cupsTempFd(tmpfilename, sizeof(tmpfilename))) < 0) + { + perror("DEBUG: Unable to create temporary file"); + return (CUPS_BACKEND_FAILED); + } - /* - * Only add a "job-name" attribute if the remote server supports - * copy generation - some IPP implementations like HP's don't seem - * to like UTF-8 job names (STR #1837)... - */ + _cupsLangPrintFilter(stderr, "INFO", _("Copying print data.")); - if (argv[3][0] && copies_sup) - ippAddString(request, IPP_TAG_OPERATION, IPP_TAG_NAME, "job-name", NULL, - argv[3]); + if ((compatsize = write(fd, buffer, bytes)) < 0) + { + perror("DEBUG: Unable to write temporary file"); + return (CUPS_BACKEND_FAILED); + } - fprintf(stderr, "DEBUG: job-name = \"%s\"\n", argv[3]); + if ((bytes = backendRunLoop(-1, fd, snmp_fd, &(addrlist->addr), 0, 0, + backendNetworkSideCB)) < 0) + return (CUPS_BACKEND_FAILED); -#ifdef HAVE_LIBZ - if (compression) - ippAddString(request, IPP_TAG_OPERATION, IPP_TAG_KEYWORD, - "compression", NULL, "gzip"); -#endif /* HAVE_LIBZ */ + compatsize += bytes; - /* - * Handle options on the command-line... - */ + close(fd); - options = NULL; - num_options = cupsParseOptions(argv[5], 0, &options); + compatfile = tmpfilename; + files = &compatfile; + num_files = 1; + } + else if (http->version < HTTP_1_1 && num_files == 1) + { + struct stat fileinfo; /* File information */ -#ifdef __APPLE__ - if (!strcasecmp(final_content_type, "application/pictwps") && - num_files == 1) - { - if (format_sup != NULL) - { - for (i = 0; i < format_sup->num_values; i ++) - if (!strcasecmp(final_content_type, format_sup->values[i].string.text)) - break; - } + if (!stat(files[0], &fileinfo)) + compatsize = fileinfo.st_size; + } - if (format_sup == NULL || i >= format_sup->num_values) - { - /* - * Remote doesn't support "application/pictwps" (i.e. it's not MacOS X) - * so convert the document to PostScript... - */ + /* + * If the printer only claims to support IPP/1.0, or if the user specifically + * included version=1.0 in the URI, then do not try to use Create-Job or + * Send-Document. This is another dreaded compatibility hack, but + * unfortunately there are enough broken printers out there that we need + * this for now... + */ - if (run_pictwps_filter(argv, files[0])) - { - if (pstmpname[0]) - unlink(pstmpname); + if (version == 10) + create_job = send_document = 0; - if (tmpfilename[0]) - unlink(tmpfilename); + /* + * Start monitoring the printer in the background... + */ - return (CUPS_BACKEND_FAILED); - } + monitor.uri = uri; + monitor.hostname = hostname; + monitor.user = argv[2]; + monitor.resource = resource; + monitor.port = port; + monitor.version = version; + monitor.job_id = 0; + monitor.get_job_attrs = get_job_attrs; + monitor.encryption = cupsEncryption(); + monitor.job_state = IPP_JOB_PENDING; + monitor.printer_state = IPP_PRINTER_IDLE; + + if (create_job) + { + monitor.job_name = argv[3]; + } + else + { + snprintf(print_job_name, sizeof(print_job_name), "%s - %s", argv[1], + argv[3]); + monitor.job_name = print_job_name; + } - files[0] = pstmpname; + _cupsThreadCreate((_cups_thread_func_t)monitor_printer, &monitor); - /* - * Change the MIME type to application/postscript and change the - * number of copies to 1... - */ + /* + * Validate access to the printer... + */ - final_content_type = "application/postscript"; - copies = 1; - copies_remaining = 1; - send_options = 0; - } - } -#endif /* __APPLE__ */ + while (!job_canceled && validate_job) + { + request = new_request(IPP_VALIDATE_JOB, version, uri, argv[2], + monitor.job_name, num_options, options, compression, + copies_sup ? copies : 1, document_format, pc, ppd, + media_col_sup, doc_handling_sup, print_color_mode); - if (format_sup != NULL) - { - for (i = 0; i < format_sup->num_values; i ++) - if (!strcasecmp(final_content_type, format_sup->values[i].string.text)) - break; + response = cupsDoRequest(http, request, resource); - if (i < format_sup->num_values) - ippAddString(request, IPP_TAG_OPERATION, IPP_TAG_MIMETYPE, - "document-format", NULL, final_content_type); + ipp_status = cupsLastError(); + + fprintf(stderr, "DEBUG: Validate-Job: %s (%s)\n", + ippErrorString(ipp_status), cupsLastErrorString()); + + if ((job_auth = ippFindAttribute(response, "job-authorization-uri", + IPP_TAG_URI)) != NULL) + num_options = cupsAddOption("job-authorization-uri", + ippGetString(job_auth, 0, NULL), num_options, + &options); + + ippDelete(response); + + if (job_canceled) + break; + + if (ipp_status == IPP_STATUS_ERROR_SERVICE_UNAVAILABLE || + ipp_status == IPP_STATUS_ERROR_BUSY) + { + _cupsLangPrintFilter(stderr, "INFO", _("The printer is in use.")); + sleep(10); } + else if (ipp_status == IPP_STATUS_ERROR_DOCUMENT_FORMAT_NOT_SUPPORTED || + ipp_status == IPP_STATUS_ERROR_CUPS_ACCOUNT_INFO_NEEDED || + ipp_status == IPP_STATUS_ERROR_CUPS_ACCOUNT_CLOSED || + ipp_status == IPP_STATUS_ERROR_CUPS_ACCOUNT_LIMIT_REACHED || + ipp_status == IPP_STATUS_ERROR_CUPS_ACCOUNT_AUTHORIZATION_FAILED) + goto cleanup; + else if (ipp_status == IPP_STATUS_ERROR_FORBIDDEN || + ipp_status == IPP_STATUS_ERROR_CUPS_AUTHENTICATION_CANCELED) + { + const char *www_auth = httpGetField(http, HTTP_FIELD_WWW_AUTHENTICATE); + /* WWW-Authenticate field value */ - if (copies_sup && version > 10 && send_options) + if (!strncmp(www_auth, "Negotiate", 9)) + auth_info_required = "negotiate"; + else if (www_auth[0]) + auth_info_required = "username,password"; + + goto cleanup; + } + else if (ipp_status == IPP_STATUS_ERROR_OPERATION_NOT_SUPPORTED) { /* - * Only send options if the destination printer supports the copies - * attribute and IPP/1.1. This is a hack for the HP and Lexmark - * implementations of IPP, which do not accept extension attributes - * and incorrectly report a client-error-bad-request error instead of - * the successful-ok-unsupported-attributes status. In short, at least - * some HP and Lexmark implementations of IPP are non-compliant. + * This is all too common... */ - cupsEncodeOptions(request, num_options, options); - - ippAddInteger(request, IPP_TAG_JOB, IPP_TAG_INTEGER, "copies", - copies); + update_reasons(NULL, "+cups-ipp-conformance-failure-report," + "cups-ipp-missing-validate-job"); + break; } + else if (ipp_status < IPP_REDIRECTION_OTHER_SITE || + ipp_status == IPP_BAD_REQUEST) + break; + } - cupsFreeOptions(num_options, options); + /* + * Then issue the print-job request... + */ + + job_id = 0; + while (!job_canceled && copies_remaining > 0) + { /* - * If copies aren't supported, then we are likely dealing with an HP - * JetDirect. The HP IPP implementation seems to close the connection - * after every request - that is, it does *not* implement HTTP Keep- - * Alive, which is REQUIRED by HTTP/1.1... + * Check for side-channel requests... */ - if (!copies_sup) - httpReconnect(http); + backendCheckSideChannel(snmp_fd, http->hostaddr); /* - * Do the request... + * Build the IPP job creation request... */ - if (http->version < HTTP_1_1) - httpReconnect(http); + if (job_canceled) + break; + + request = new_request((num_files > 1 || create_job) ? IPP_CREATE_JOB : + IPP_PRINT_JOB, + version, uri, argv[2], monitor.job_name, num_options, + options, compression, copies_sup ? copies : 1, + document_format, pc, ppd, media_col_sup, + doc_handling_sup, print_color_mode); + + /* + * Do the request... + */ - if (num_files > 1) + if (num_files > 1 || create_job) response = cupsDoRequest(http, request, resource); else - response = cupsDoFileRequest(http, request, resource, files[0]); + { + size_t length = 0; /* Length of request */ + + if (compatsize > 0) + { + fputs("DEBUG: Sending file using HTTP/1.0 Content-Length...\n", stderr); + length = ippLength(request) + (size_t)compatsize; + } + else + fputs("DEBUG: Sending file using HTTP/1.1 chunking...\n", stderr); + + http_status = cupsSendRequest(http, request, resource, length); + if (http_status == HTTP_CONTINUE && request->state == IPP_DATA) + { + if (compression && strcmp(compression, "none")) + httpSetField(http, HTTP_FIELD_CONTENT_ENCODING, compression); + + if (num_files == 1) + { + if ((fd = open(files[0], O_RDONLY)) < 0) + { + _cupsLangPrintError("ERROR", _("Unable to open print file")); + return (CUPS_BACKEND_FAILED); + } + } + else + { + fd = 0; + http_status = cupsWriteRequestData(http, buffer, bytes); + } + + while (http_status == HTTP_CONTINUE && + (!job_canceled || compatsize > 0)) + { + /* + * Check for side-channel requests and more print data... + */ + + FD_ZERO(&input); + FD_SET(fd, &input); + FD_SET(snmp_fd, &input); + + while (select(fd > snmp_fd ? fd + 1 : snmp_fd + 1, &input, NULL, NULL, + NULL) <= 0 && !job_canceled); + + if (FD_ISSET(snmp_fd, &input)) + backendCheckSideChannel(snmp_fd, http->hostaddr); + + if (FD_ISSET(fd, &input)) + { + if ((bytes = read(fd, buffer, sizeof(buffer))) > 0) + { + fprintf(stderr, "DEBUG: Read %d bytes...\n", (int)bytes); + + if ((http_status = cupsWriteRequestData(http, buffer, bytes)) + != HTTP_CONTINUE) + break; + } + else if (bytes == 0 || (errno != EINTR && errno != EAGAIN)) + break; + } + } + + if (http_status == HTTP_ERROR) + fprintf(stderr, "DEBUG: Error writing document data for " + "Print-Job: %s\n", strerror(httpError(http))); + + if (num_files == 1) + close(fd); + } + + response = cupsGetResponse(http, resource); + ippDelete(request); + } ipp_status = cupsLastError(); + fprintf(stderr, "DEBUG: %s: %s (%s)\n", + (num_files > 1 || create_job) ? "Create-Job" : "Print-Job", + ippErrorString(ipp_status), cupsLastErrorString()); + if (ipp_status > IPP_OK_CONFLICT) { job_id = 0; - if (job_cancelled) + if (job_canceled) break; - if (ipp_status == IPP_SERVICE_UNAVAILABLE || - ipp_status == IPP_PRINTER_BUSY) + if (ipp_status == IPP_STATUS_ERROR_SERVICE_UNAVAILABLE || + ipp_status == IPP_STATUS_ERROR_NOT_POSSIBLE || + ipp_status == IPP_STATUS_ERROR_BUSY) { - _cupsLangPuts(stderr, - _("INFO: Printer busy; will retry in 10 seconds...\n")); + _cupsLangPrintFilter(stderr, "INFO", _("The printer is in use.")); sleep(10); - } - else if ((ipp_status == IPP_BAD_REQUEST || - ipp_status == IPP_VERSION_NOT_SUPPORTED) && version > 10) - { - /* - * Switch to IPP/1.0... - */ - _cupsLangPrintf(stderr, - _("INFO: Printer does not support IPP/%d.%d, trying " - "IPP/1.0...\n"), version / 10, version % 10); - version = 10; - httpReconnect(http); + if (num_files == 0) + { + /* + * We can't re-submit when we have no files to print, so exit + * immediately with the right status code... + */ + + goto cleanup; + } } + else if (ipp_status == IPP_STATUS_ERROR_JOB_CANCELED || + ipp_status == IPP_STATUS_ERROR_NOT_AUTHORIZED || + ipp_status == IPP_STATUS_ERROR_CUPS_ACCOUNT_INFO_NEEDED || + ipp_status == IPP_STATUS_ERROR_CUPS_ACCOUNT_CLOSED || + ipp_status == IPP_STATUS_ERROR_CUPS_ACCOUNT_LIMIT_REACHED || + ipp_status == IPP_STATUS_ERROR_CUPS_ACCOUNT_AUTHORIZATION_FAILED) + goto cleanup; else { /* * Update auth-info-required as needed... */ - _cupsLangPrintf(stderr, _("ERROR: Print file was not accepted (%s)!\n"), - cupsLastErrorString()); + _cupsLangPrintFilter(stderr, "ERROR", + _("Print job was not accepted.")); - if (ipp_status == IPP_NOT_AUTHORIZED) + if (ipp_status == IPP_STATUS_ERROR_FORBIDDEN || + ipp_status == IPP_STATUS_ERROR_CUPS_AUTHENTICATION_CANCELED) { - fprintf(stderr, "DEBUG: WWW-Authenticate=\"%s\"\n", - httpGetField(http, HTTP_FIELD_WWW_AUTHENTICATE)); + const char *www_auth = httpGetField(http, HTTP_FIELD_WWW_AUTHENTICATE); + /* WWW-Authenticate field value */ - if (!strncmp(httpGetField(http, HTTP_FIELD_WWW_AUTHENTICATE), - "Negotiate", 9)) - fputs("ATTR: auth-info-required=negotiate\n", stderr); - else - fputs("ATTR: auth-info-required=username,password\n", stderr); + if (!strncmp(www_auth, "Negotiate", 9)) + auth_info_required = "negotiate"; + else if (www_auth[0]) + auth_info_required = "username,password"; + } + else if (ipp_status == IPP_REQUEST_VALUE) + { + /* + * Print file is too large, abort this job... + */ + + goto cleanup; + } + else + sleep(10); + + if (num_files == 0) + { + /* + * We can't re-submit when we have no files to print, so exit + * immediately with the right status code... + */ + + goto cleanup; } } } else if ((job_id_attr = ippFindAttribute(response, "job-id", IPP_TAG_INTEGER)) == NULL) { - _cupsLangPuts(stderr, - _("NOTICE: Print file accepted - job ID unknown.\n")); + fputs("DEBUG: Print job accepted - job ID unknown.\n", stderr); + update_reasons(NULL, "+cups-ipp-conformance-failure-report," + "cups-ipp-missing-job-id"); job_id = 0; } else { - job_id = job_id_attr->values[0].integer; - _cupsLangPrintf(stderr, _("NOTICE: Print file accepted - job ID %d.\n"), - job_id); + password_tries = 0; + monitor.job_id = job_id = job_id_attr->values[0].integer; + fprintf(stderr, "DEBUG: Print job accepted - job ID %d.\n", job_id); } ippDelete(response); - if (job_cancelled) + if (job_canceled) break; - if (job_id && num_files > 1) + if (job_id && (num_files > 1 || create_job)) { - for (i = 0; i < num_files; i ++) + for (i = 0; num_files == 0 || i < num_files; i ++) { /* * Check for side-channel requests... @@ -1126,26 +1698,88 @@ main(int argc, /* I - Number of command-line args */ ippAddString(request, IPP_TAG_OPERATION, IPP_TAG_NAME, "requesting-user-name", NULL, argv[2]); - if ((i + 1) == num_files) + if ((i + 1) >= num_files) ippAddBoolean(request, IPP_TAG_OPERATION, "last-document", 1); - ippAddString(request, IPP_TAG_OPERATION, IPP_TAG_MIMETYPE, - "document-format", NULL, content_type); + if (document_format) + ippAddString(request, IPP_TAG_OPERATION, IPP_TAG_MIMETYPE, + "document-format", NULL, document_format); + + if (compression) + ippAddString(request, IPP_TAG_OPERATION, IPP_TAG_KEYWORD, + "compression", NULL, compression); + + fprintf(stderr, "DEBUG: Sending file %d using chunking...\n", i + 1); + http_status = cupsSendRequest(http, request, resource, 0); + if (http_status == HTTP_CONTINUE && request->state == IPP_DATA) + { + if (compression && strcmp(compression, "none")) + httpSetField(http, HTTP_FIELD_CONTENT_ENCODING, compression); + + if (num_files == 0) + { + fd = 0; + http_status = cupsWriteRequestData(http, buffer, bytes); + } + else + { + if ((fd = open(files[i], O_RDONLY)) < 0) + { + _cupsLangPrintError("ERROR", _("Unable to open print file")); + return (CUPS_BACKEND_FAILED); + } + } + } + else + fd = -1; + + if (fd >= 0) + { + while (!job_canceled && http_status == HTTP_CONTINUE && + (bytes = read(fd, buffer, sizeof(buffer))) > 0) + { + if ((http_status = cupsWriteRequestData(http, buffer, bytes)) + != HTTP_CONTINUE) + break; + else + { + /* + * Check for side-channel requests... + */ + + backendCheckSideChannel(snmp_fd, http->hostaddr); + } + } + + if (fd > 0) + close(fd); + } + + if (http_status == HTTP_ERROR) + fprintf(stderr, "DEBUG: Error writing document data for " + "Send-Document: %s\n", strerror(httpError(http))); - if (http->version < HTTP_1_1) - httpReconnect(http); + ippDelete(cupsGetResponse(http, resource)); + ippDelete(request); - ippDelete(cupsDoFileRequest(http, request, resource, files[i])); + fprintf(stderr, "DEBUG: Send-Document: %s (%s)\n", + ippErrorString(cupsLastError()), cupsLastErrorString()); if (cupsLastError() > IPP_OK_CONFLICT) { ipp_status = cupsLastError(); - _cupsLangPrintf(stderr, - _("ERROR: Unable to add file %d to job: %s\n"), - job_id, cupsLastErrorString()); + _cupsLangPrintFilter(stderr, "ERROR", + _("Unable to add document to print job.")); break; } + else + { + password_tries = 0; + + if (num_files == 0 || fd < 0) + break; + } } } @@ -1155,8 +1789,78 @@ main(int argc, /* I - Number of command-line args */ copies_remaining --; } else if (ipp_status == IPP_SERVICE_UNAVAILABLE || + ipp_status == IPP_NOT_POSSIBLE || ipp_status == IPP_PRINTER_BUSY) + { + if (argc == 6) + { + /* + * Need to reprocess the entire job; if we have a job ID, cancel the + * job first... + */ + + if (job_id > 0) + cancel_job(http, uri, job_id, resource, argv[2], version); + + goto cleanup; + } continue; + } + else if (ipp_status == IPP_REQUEST_VALUE || + ipp_status == IPP_ERROR_JOB_CANCELED || + ipp_status == IPP_NOT_AUTHORIZED || + ipp_status == IPP_STATUS_ERROR_CUPS_ACCOUNT_INFO_NEEDED || + ipp_status == IPP_STATUS_ERROR_CUPS_ACCOUNT_CLOSED || + ipp_status == IPP_STATUS_ERROR_CUPS_ACCOUNT_LIMIT_REACHED || + ipp_status == IPP_STATUS_ERROR_CUPS_ACCOUNT_AUTHORIZATION_FAILED || + ipp_status == IPP_INTERNAL_ERROR) + { + /* + * Print file is too large, job was canceled, we need new + * authentication data, or we had some sort of error... + */ + + goto cleanup; + } + else if (ipp_status == IPP_STATUS_ERROR_CUPS_UPGRADE_REQUIRED) + { + /* + * Server is configured incorrectly; the policy for Create-Job and + * Send-Document has to be the same (auth or no auth, encryption or + * no encryption). Force the queue to stop since printing will never + * work. + */ + + fputs("DEBUG: The server or printer is configured incorrectly.\n", + stderr); + fputs("DEBUG: The policy for Create-Job and Send-Document must have the " + "same authentication and encryption requirements.\n", stderr); + + ipp_status = IPP_STATUS_ERROR_INTERNAL; + + if (job_id > 0) + cancel_job(http, uri, job_id, resource, argv[2], version); + + goto cleanup; + } + else if (ipp_status == IPP_NOT_FOUND) + { + /* + * Printer does not actually implement support for Create-Job/ + * Send-Document, so log the conformance issue and stop the printer. + */ + + fputs("DEBUG: This printer claims to support Create-Job and " + "Send-Document, but those operations failed.\n", stderr); + fputs("DEBUG: Add '?version=1.0' to the device URI to use legacy " + "compatibility mode.\n", stderr); + update_reasons(NULL, "+cups-ipp-conformance-failure-report," + "cups-ipp-missing-send-document"); + + ipp_status = IPP_INTERNAL_ERROR; /* Force queue to stop */ + + goto cleanup; + } else copies_remaining --; @@ -1164,12 +1868,12 @@ main(int argc, /* I - Number of command-line args */ * Wait for the job to complete... */ - if (!job_id || !waitjob) + if (!job_id || !waitjob || !get_job_attrs) continue; - _cupsLangPuts(stderr, _("INFO: Waiting for job to complete...\n")); + _cupsLangPrintFilter(stderr, "INFO", _("Waiting for job to complete.")); - for (delay = 1; !job_cancelled;) + for (delay = _cupsNextDelay(0, &prev_delay); !job_canceled;) { /* * Check for side-channel requests... @@ -1177,6 +1881,12 @@ main(int argc, /* I - Number of command-line args */ backendCheckSideChannel(snmp_fd, http->hostaddr); + /* + * Check printer state... + */ + + check_printer_state(http, uri, resource, argv[2], version); + /* * Build an IPP_GET_JOB_ATTRIBUTES request... */ @@ -1203,36 +1913,49 @@ main(int argc, /* I - Number of command-line args */ * Do the request... */ - if (!copies_sup || http->version < HTTP_1_1) - httpReconnect(http); - + httpReconnect(http); response = cupsDoRequest(http, request, resource); ipp_status = cupsLastError(); - if (ipp_status == IPP_NOT_FOUND) + if (ipp_status == IPP_NOT_FOUND || ipp_status == IPP_NOT_POSSIBLE) { /* * Job has gone away and/or the server has no job history... */ + update_reasons(NULL, "+cups-ipp-conformance-failure-report," + "cups-ipp-missing-job-history"); ippDelete(response); ipp_status = IPP_OK; break; } - if (ipp_status > IPP_OK_CONFLICT) + fprintf(stderr, "DEBUG: Get-Job-Attributes: %s (%s)\n", + ippErrorString(ipp_status), cupsLastErrorString()); + + if (ipp_status <= IPP_OK_CONFLICT) + password_tries = 0; + else { if (ipp_status != IPP_SERVICE_UNAVAILABLE && ipp_status != IPP_PRINTER_BUSY) { ippDelete(response); - - _cupsLangPrintf(stderr, - _("ERROR: Unable to get job %d attributes (%s)!\n"), - job_id, cupsLastErrorString()); + ipp_status = IPP_OK; break; } + else if (ipp_status == IPP_INTERNAL_ERROR) + { + waitjob_tries ++; + + if (waitjob_tries > 4) + { + ippDelete(response); + ipp_status = IPP_OK; + break; + } + } } if (response) @@ -1240,41 +1963,64 @@ main(int argc, /* I - Number of command-line args */ if ((job_state = ippFindAttribute(response, "job-state", IPP_TAG_ENUM)) != NULL) { + /* + * Reflect the remote job state in the local queue... + */ + + if (cups_version && + job_state->values[0].integer >= IPP_JOB_PENDING && + job_state->values[0].integer <= IPP_JOB_COMPLETED) + update_reasons(NULL, + remote_job_states[job_state->values[0].integer - + IPP_JOB_PENDING]); + + if ((job_sheets = ippFindAttribute(response, + "job-media-sheets-completed", + IPP_TAG_INTEGER)) == NULL) + job_sheets = ippFindAttribute(response, + "job-impressions-completed", + IPP_TAG_INTEGER); + + if (job_sheets) + fprintf(stderr, "PAGE: total %d\n", + job_sheets->values[0].integer); + /* * Stop polling if the job is finished or pending-held... */ if (job_state->values[0].integer > IPP_JOB_STOPPED) { - if ((job_sheets = ippFindAttribute(response, - "job-media-sheets-completed", - IPP_TAG_INTEGER)) != NULL) - fprintf(stderr, "PAGE: total %d\n", - job_sheets->values[0].integer); - ippDelete(response); break; } } + else if (ipp_status != IPP_SERVICE_UNAVAILABLE && + ipp_status != IPP_NOT_POSSIBLE && + ipp_status != IPP_PRINTER_BUSY) + { + /* + * If the printer does not return a job-state attribute, it does not + * conform to the IPP specification - break out immediately and fail + * the job... + */ + + update_reasons(NULL, "+cups-ipp-conformance-failure-report," + "cups-ipp-missing-job-state"); + ipp_status = IPP_INTERNAL_ERROR; + break; + } } ippDelete(response); /* - * Check the printer state and report it if necessary... - */ - - check_printer_state(http, uri, resource, argv[2], version, job_id); - - /* - * Wait 1-10 seconds before polling again... + * Wait before polling again... */ sleep(delay); - delay ++; - if (delay > 10) - delay = 1; + delay = _cupsNextDelay(delay, &prev_delay); } } @@ -1282,28 +2028,44 @@ main(int argc, /* I - Number of command-line args */ * Cancel the job as needed... */ - if (job_cancelled && job_id) + if (job_canceled > 0 && job_id > 0) cancel_job(http, uri, job_id, resource, argv[2], version); /* * Check the printer state and report it if necessary... */ - check_printer_state(http, uri, resource, argv[2], version, job_id); + check_printer_state(http, uri, resource, argv[2], version); /* * Collect the final page count as needed... */ - if (have_supplies && - !backendSNMPSupplies(snmp_fd, http->hostaddr, &page_count, NULL) && + if (have_supplies && + !backendSNMPSupplies(snmp_fd, &(http->addrlist->addr), &page_count, + NULL) && page_count > start_count) fprintf(stderr, "PAGE: total %d\n", page_count - start_count); +#ifdef HAVE_GSSAPI + /* + * See if we used Kerberos at all... + */ + + if (http->gssctx) + auth_info_required = "negotiate"; +#endif /* HAVE_GSSAPI */ + /* * Free memory... */ + cleanup: + + cupsFreeOptions(num_options, options); + _ppdCacheDestroy(pc); + ppdClose(ppd); + httpClose(http); ippDelete(supported); @@ -1315,27 +2077,50 @@ main(int argc, /* I - Number of command-line args */ if (tmpfilename[0]) unlink(tmpfilename); -#ifdef HAVE_LIBZ - if (compression) - { - for (i = 0; i < num_files; i ++) - unlink(files[i]); - } -#endif /* HAVE_LIBZ */ - -#ifdef __APPLE__ - if (pstmpname[0]) - unlink(pstmpname); -#endif /* __APPLE__ */ - /* * Return the queue status... */ - if (ipp_status == IPP_NOT_AUTHORIZED) + if (ipp_status == IPP_NOT_AUTHORIZED || ipp_status == IPP_FORBIDDEN || + ipp_status == IPP_AUTHENTICATION_CANCELED || + ipp_status <= IPP_OK_CONFLICT) + fprintf(stderr, "ATTR: auth-info-required=%s\n", auth_info_required); + + if (ipp_status == IPP_STATUS_ERROR_CUPS_ACCOUNT_INFO_NEEDED) + fputs("JOBSTATE: account-info-needed\n", stderr); + else if (ipp_status == IPP_STATUS_ERROR_CUPS_ACCOUNT_CLOSED) + fputs("JOBSTATE: account-closed\n", stderr); + else if (ipp_status == IPP_STATUS_ERROR_CUPS_ACCOUNT_LIMIT_REACHED) + fputs("JOBSTATE: account-limit-reached\n", stderr); + else if (ipp_status == IPP_STATUS_ERROR_CUPS_ACCOUNT_AUTHORIZATION_FAILED) + fputs("JOBSTATE: account-authorization-failed\n", stderr); + + if (ipp_status == IPP_NOT_AUTHORIZED || ipp_status == IPP_FORBIDDEN || + ipp_status == IPP_AUTHENTICATION_CANCELED || + ipp_status == IPP_STATUS_ERROR_CUPS_ACCOUNT_INFO_NEEDED || + ipp_status == IPP_STATUS_ERROR_CUPS_ACCOUNT_CLOSED || + ipp_status == IPP_STATUS_ERROR_CUPS_ACCOUNT_LIMIT_REACHED || + ipp_status == IPP_STATUS_ERROR_CUPS_ACCOUNT_AUTHORIZATION_FAILED) return (CUPS_BACKEND_AUTH_REQUIRED); - else if (ipp_status > IPP_OK_CONFLICT) + else if (ipp_status == IPP_INTERNAL_ERROR) + return (CUPS_BACKEND_STOP); + else if (ipp_status == IPP_CONFLICT) return (CUPS_BACKEND_FAILED); + else if (ipp_status == IPP_REQUEST_VALUE || + ipp_status == IPP_DOCUMENT_FORMAT || job_canceled < 0) + { + if (ipp_status == IPP_REQUEST_VALUE) + _cupsLangPrintFilter(stderr, "ERROR", _("Print job too large.")); + else if (ipp_status == IPP_DOCUMENT_FORMAT) + _cupsLangPrintFilter(stderr, "ERROR", + _("Printer cannot print supplied content.")); + else + _cupsLangPrintFilter(stderr, "ERROR", _("Print job canceled at printer.")); + + return (CUPS_BACKEND_CANCEL); + } + else if (ipp_status > IPP_OK_CONFLICT && ipp_status != IPP_ERROR_JOB_CANCELED) + return (CUPS_BACKEND_RETRY_CURRENT); else return (CUPS_BACKEND_OK); } @@ -1356,7 +2141,7 @@ cancel_job(http_t *http, /* I - HTTP connection */ ipp_t *request; /* Cancel-Job request */ - _cupsLangPuts(stderr, _("INFO: Canceling print job...\n")); + _cupsLangPrintFilter(stderr, "INFO", _("Canceling print job.")); request = ippNewRequest(IPP_CANCEL_JOB); request->request.op.version[0] = version / 10; @@ -1374,47 +2159,34 @@ cancel_job(http_t *http, /* I - HTTP connection */ * Do the request... */ - if (http->version < HTTP_1_1) - httpReconnect(http); - ippDelete(cupsDoRequest(http, request, resource)); if (cupsLastError() > IPP_OK_CONFLICT) - _cupsLangPrintf(stderr, _("ERROR: Unable to cancel job %d: %s\n"), id, - cupsLastErrorString()); + _cupsLangPrintFilter(stderr, "ERROR", _("Unable to cancel print job.")); } /* - * 'check_printer_state()' - Check the printer state... + * 'check_printer_state()' - Check the printer state. */ -static void +static ipp_pstate_t /* O - Current printer-state */ check_printer_state( http_t *http, /* I - HTTP connection */ const char *uri, /* I - Printer URI */ const char *resource, /* I - Resource path */ const char *user, /* I - Username, if any */ - int version, /* I - IPP version */ - int job_id) /* I - Current job ID */ -{ - ipp_t *request, /* IPP request */ - *response; /* IPP response */ - static const char * const attrs[] = /* Attributes we want */ - { - "com.apple.print.recoverable-message", - "marker-colors", - "marker-levels", - "marker-message", - "marker-names", - "marker-types", - "printer-state-message", - "printer-state-reasons" - }; + int version) /* I - IPP version */ + { + ipp_t *request, /* IPP request */ + *response; /* IPP response */ + ipp_attribute_t *attr; /* Attribute in response */ + ipp_pstate_t printer_state = IPP_PRINTER_STOPPED; + /* Current printer-state */ /* - * Check on the printer state... + * Send a Get-Printer-Attributes request and log the results... */ request = ippNewRequest(IPP_GET_PRINTER_ATTRIBUTES); @@ -1422,107 +2194,728 @@ check_printer_state( request->request.op.version[1] = version % 10; ippAddString(request, IPP_TAG_OPERATION, IPP_TAG_URI, "printer-uri", - NULL, uri); + NULL, uri); if (user && user[0]) ippAddString(request, IPP_TAG_OPERATION, IPP_TAG_NAME, - "requesting-user-name", NULL, user); + "requesting-user-name", NULL, user); ippAddStrings(request, IPP_TAG_OPERATION, IPP_TAG_KEYWORD, - "requested-attributes", - (int)(sizeof(attrs) / sizeof(attrs[0])), NULL, attrs); + "requested-attributes", + (int)(sizeof(pattrs) / sizeof(pattrs[0])), NULL, pattrs); + + if ((response = cupsDoRequest(http, request, resource)) != NULL) + { + report_printer_state(response); + + if ((attr = ippFindAttribute(response, "printer-state", + IPP_TAG_ENUM)) != NULL) + printer_state = (ipp_pstate_t)attr->values[0].integer; + + ippDelete(response); + } + + fprintf(stderr, "DEBUG: Get-Printer-Attributes: %s (%s)\n", + ippErrorString(cupsLastError()), cupsLastErrorString()); + + if (cupsLastError() <= IPP_OK_CONFLICT) + password_tries = 0; + + /* + * Return the printer-state value... + */ + + return (printer_state); +} + + +/* + * 'monitor_printer()' - Monitor the printer state. + */ + +static void * /* O - Thread exit code */ +monitor_printer( + _cups_monitor_t *monitor) /* I - Monitoring data */ +{ + http_t *http; /* Connection to printer */ + ipp_t *request, /* IPP request */ + *response; /* IPP response */ + ipp_attribute_t *attr; /* Attribute in response */ + int delay, /* Current delay */ + prev_delay; /* Previous delay */ + ipp_op_t job_op; /* Operation to use */ + int job_id; /* Job ID */ + const char *job_name; /* Job name */ + ipp_jstate_t job_state; /* Job state */ + const char *job_user; /* Job originating user name */ + + + /* + * Make a copy of the printer connection... + */ + + http = httpConnect2(monitor->hostname, monitor->port, NULL, AF_UNSPEC, + monitor->encryption, 1, 0, NULL); + httpSetTimeout(http, 30.0, timeout_cb, NULL); + if (username[0]) + cupsSetUser(username); + cupsSetPasswordCB2(password_cb, NULL); + + /* + * Loop until the job is canceled, aborted, or completed. + */ + + delay = _cupsNextDelay(0, &prev_delay); + + monitor->job_reasons = 0; + + while (monitor->job_state < IPP_JOB_CANCELED && !job_canceled) + { + /* + * Reconnect to the printer... + */ + + if (!httpReconnect(http)) + { + /* + * Connected, so check on the printer state... + */ + + monitor->printer_state = check_printer_state(http, monitor->uri, + monitor->resource, + monitor->user, + monitor->version); + + /* + * Check the status of the job itself... + */ + + job_op = (monitor->job_id > 0 && monitor->get_job_attrs) ? + IPP_GET_JOB_ATTRIBUTES : IPP_GET_JOBS; + request = ippNewRequest(job_op); + request->request.op.version[0] = monitor->version / 10; + request->request.op.version[1] = monitor->version % 10; + + ippAddString(request, IPP_TAG_OPERATION, IPP_TAG_URI, "printer-uri", + NULL, monitor->uri); + if (job_op == IPP_GET_JOB_ATTRIBUTES) + ippAddInteger(request, IPP_TAG_OPERATION, IPP_TAG_INTEGER, "job-id", + monitor->job_id); + + if (monitor->user && monitor->user[0]) + ippAddString(request, IPP_TAG_OPERATION, IPP_TAG_NAME, + "requesting-user-name", NULL, monitor->user); + + ippAddStrings(request, IPP_TAG_OPERATION, IPP_TAG_KEYWORD, + "requested-attributes", + (int)(sizeof(jattrs) / sizeof(jattrs[0])), NULL, jattrs); + + /* + * Do the request... + */ + + response = cupsDoRequest(http, request, monitor->resource); + + fprintf(stderr, "DEBUG: (monitor) %s: %s (%s)\n", ippOpString(job_op), + ippErrorString(cupsLastError()), cupsLastErrorString()); + + if (cupsLastError() <= IPP_OK_CONFLICT) + password_tries = 0; + + if (job_op == IPP_GET_JOB_ATTRIBUTES) + { + if ((attr = ippFindAttribute(response, "job-state", + IPP_TAG_ENUM)) != NULL) + monitor->job_state = (ipp_jstate_t)attr->values[0].integer; + else + monitor->job_state = IPP_JOB_COMPLETED; + } + else if (response) + { + for (attr = response->attrs; attr; attr = attr->next) + { + job_id = 0; + job_name = NULL; + job_state = IPP_JOB_PENDING; + job_user = NULL; + + while (attr && attr->group_tag != IPP_TAG_JOB) + attr = attr->next; + + if (!attr) + break; + + while (attr && attr->group_tag == IPP_TAG_JOB) + { + if (!strcmp(attr->name, "job-id") && + attr->value_tag == IPP_TAG_INTEGER) + job_id = attr->values[0].integer; + else if (!strcmp(attr->name, "job-name") && + (attr->value_tag == IPP_TAG_NAME || + attr->value_tag == IPP_TAG_NAMELANG)) + job_name = attr->values[0].string.text; + else if (!strcmp(attr->name, "job-state") && + attr->value_tag == IPP_TAG_ENUM) + job_state = attr->values[0].integer; + else if (!strcmp(attr->name, "job-originating-user-name") && + (attr->value_tag == IPP_TAG_NAME || + attr->value_tag == IPP_TAG_NAMELANG)) + job_user = attr->values[0].string.text; + + attr = attr->next; + } + + if (job_id > 0 && job_name && !strcmp(job_name, monitor->job_name) && + job_user && monitor->user && !strcmp(job_user, monitor->user)) + { + monitor->job_id = job_id; + monitor->job_state = job_state; + break; + } + + if (!attr) + break; + } + } + + if ((attr = ippFindAttribute(response, "job-state-reasons", + IPP_TAG_KEYWORD)) != NULL) + { + int i, new_reasons = 0; /* Looping var, new reasons */ + + for (i = 0; i < attr->num_values; i ++) + { + if (!strcmp(attr->values[i].string.text, + "account-authorization-failed")) + new_reasons |= _CUPS_JSR_ACCOUNT_AUTHORIZATION_FAILED; + else if (!strcmp(attr->values[i].string.text, "account-closed")) + new_reasons |= _CUPS_JSR_ACCOUNT_CLOSED; + else if (!strcmp(attr->values[i].string.text, "account-info-needed")) + new_reasons |= _CUPS_JSR_ACCOUNT_INFO_NEEDED; + else if (!strcmp(attr->values[i].string.text, + "account-limit-reached")) + new_reasons |= _CUPS_JSR_ACCOUNT_LIMIT_REACHED; + else if (!strcmp(attr->values[i].string.text, "job-password-wait")) + new_reasons |= _CUPS_JSR_JOB_PASSWORD_WAIT; + else if (!strcmp(attr->values[i].string.text, "job-release-wait")) + new_reasons |= _CUPS_JSR_JOB_RELEASE_WAIT; + } + + if (new_reasons != monitor->job_reasons) + { + if (new_reasons & _CUPS_JSR_ACCOUNT_AUTHORIZATION_FAILED) + fputs("JOBSTATE: account-authorization-failed\n", stderr); + else if (new_reasons & _CUPS_JSR_ACCOUNT_CLOSED) + fputs("JOBSTATE: account-closed\n", stderr); + else if (new_reasons & _CUPS_JSR_ACCOUNT_INFO_NEEDED) + fputs("JOBSTATE: account-info-needed\n", stderr); + else if (new_reasons & _CUPS_JSR_ACCOUNT_LIMIT_REACHED) + fputs("JOBSTATE: account-limit-reached\n", stderr); + else if (new_reasons & _CUPS_JSR_JOB_PASSWORD_WAIT) + fputs("JOBSTATE: job-password-wait\n", stderr); + else if (new_reasons & _CUPS_JSR_JOB_RELEASE_WAIT) + fputs("JOBSTATE: job-release-wait\n", stderr); + else + fputs("JOBSTATE: job-printing\n", stderr); + + monitor->job_reasons = new_reasons; + } + } + + ippDelete(response); + + fprintf(stderr, "DEBUG: (monitor) job-state=%s\n", + ippEnumString("job-state", monitor->job_state)); + + if (!job_canceled && + (monitor->job_state == IPP_JOB_CANCELED || + monitor->job_state == IPP_JOB_ABORTED)) + job_canceled = -1; + + /* + * Disconnect from the printer - we'll reconnect on the next poll... + */ + + _httpDisconnect(http); + } + + /* + * Sleep for N seconds... + */ + + sleep(delay); + + delay = _cupsNextDelay(delay, &prev_delay); + } + + /* + * Cancel the job if necessary... + */ + + if (job_canceled > 0 && monitor->job_id > 0) + if (!httpReconnect(http)) + cancel_job(http, monitor->uri, monitor->job_id, monitor->resource, + monitor->user, monitor->version); + + /* + * Cleanup and return... + */ + + httpClose(http); + + return (NULL); +} + + +/* + * 'new_request()' - Create a new print creation or validation request. + */ + +static ipp_t * /* O - Request data */ +new_request( + ipp_op_t op, /* I - IPP operation code */ + int version, /* I - IPP version number */ + const char *uri, /* I - printer-uri value */ + const char *user, /* I - requesting-user-name value */ + const char *title, /* I - job-name value */ + int num_options, /* I - Number of options to send */ + cups_option_t *options, /* I - Options to send */ + const char *compression, /* I - compression value or NULL */ + int copies, /* I - copies value or 0 */ + const char *format, /* I - document-format value or NULL */ + _ppd_cache_t *pc, /* I - PPD cache and mapping data */ + ppd_file_t *ppd, /* I - PPD file data */ + ipp_attribute_t *media_col_sup, /* I - media-col-supported values */ + ipp_attribute_t *doc_handling_sup, /* I - multiple-document-handling-supported values */ + int print_color_mode) /* I - Printer supports print-color-mode */ +{ + int i; /* Looping var */ + ipp_t *request; /* Request data */ + const char *keyword; /* PWG keyword */ + _pwg_size_t *size; /* PWG media size */ + ipp_t *media_col, /* media-col value */ + *media_size; /* media-size value */ + const char *media_source, /* media-source value */ + *media_type, /* media-type value */ + *collate_str, /* multiple-document-handling value */ + *mandatory; /* Mandatory attributes */ + ipp_tag_t group; /* Current group */ + ipp_attribute_t *attr; /* Current attribute */ + const char *color_attr_name; /* Supported color attribute */ + char buffer[1024]; /* Value buffer */ + + + /* + * Create the IPP request... + */ + + request = ippNewRequest(op); + request->request.op.version[0] = version / 10; + request->request.op.version[1] = version % 10; + + fprintf(stderr, "DEBUG: %s IPP/%d.%d\n", + ippOpString(request->request.op.operation_id), + request->request.op.version[0], + request->request.op.version[1]); + + /* + * Add standard attributes... + */ + + ippAddString(request, IPP_TAG_OPERATION, IPP_TAG_URI, "printer-uri", + NULL, uri); + fprintf(stderr, "DEBUG: printer-uri=\"%s\"\n", uri); + + if (user && *user) + { + ippAddString(request, IPP_TAG_OPERATION, IPP_TAG_NAME, + "requesting-user-name", NULL, user); + fprintf(stderr, "DEBUG: requesting-user-name=\"%s\"\n", user); + } + + if (title && *title) + { + ippAddString(request, IPP_TAG_OPERATION, IPP_TAG_NAME, "job-name", NULL, + title); + fprintf(stderr, "DEBUG: job-name=\"%s\"\n", title); + } + + if (format && op != IPP_CREATE_JOB) + { + ippAddString(request, IPP_TAG_OPERATION, IPP_TAG_MIMETYPE, + "document-format", NULL, format); + fprintf(stderr, "DEBUG: document-format=\"%s\"\n", format); + } + +#ifdef HAVE_LIBZ + if (compression && op != IPP_OP_CREATE_JOB && op != IPP_OP_VALIDATE_JOB) + { + ippAddString(request, IPP_TAG_OPERATION, IPP_TAG_KEYWORD, + "compression", NULL, compression); + fprintf(stderr, "DEBUG: compression=\"%s\"\n", compression); + } +#endif /* HAVE_LIBZ */ + + /* + * Handle options on the command-line... + */ + + if (num_options > 0) + { + if (pc) + { + int num_finishings = 0, /* Number of finishing values */ + finishings[10]; /* Finishing enum values */ + ppd_choice_t *choice; /* Marked choice */ + + /* + * Send standard IPP attributes... + */ + + fputs("DEBUG: Adding standard IPP operation/job attributes.\n", stderr); + + if (pc->password && + (keyword = cupsGetOption("job-password", num_options, + options)) != NULL) + { + ippAddOctetString(request, IPP_TAG_OPERATION, "job-password", + keyword, strlen(keyword)); + + if ((keyword = cupsGetOption("job-password-encryption", num_options, + options)) == NULL) + keyword = "none"; + + ippAddString(request, IPP_TAG_OPERATION, IPP_TAG_KEYWORD, + "job-password-encryption", NULL, keyword); + } + + if (pc->account_id && + (keyword = cupsGetOption("job-account-id", num_options, + options)) != NULL) + ippAddString(request, IPP_TAG_JOB, IPP_TAG_NAME, "job-account-id", + NULL, keyword); + + if (pc->account_id && + (keyword = cupsGetOption("job-accounting-user-id", num_options, + options)) != NULL) + ippAddString(request, IPP_TAG_JOB, IPP_TAG_NAME, + "job-accounting-user-id", NULL, keyword); + + for (mandatory = (char *)cupsArrayFirst(pc->mandatory); + mandatory; + mandatory = (char *)cupsArrayNext(pc->mandatory)) + { + if (strcmp(mandatory, "copies") && + strcmp(mandatory, "destination-uris") && + strcmp(mandatory, "finishings") && + strcmp(mandatory, "job-account-id") && + strcmp(mandatory, "job-accounting-user-id") && + strcmp(mandatory, "job-password") && + strcmp(mandatory, "job-password-encryption") && + strcmp(mandatory, "media") && + strncmp(mandatory, "media-col", 9) && + strcmp(mandatory, "multiple-document-handling") && + strcmp(mandatory, "output-bin") && + strcmp(mandatory, "print-color-mode") && + strcmp(mandatory, "print-quality") && + strcmp(mandatory, "sides") && + (keyword = cupsGetOption(mandatory, num_options, options)) != NULL) + { + _ipp_option_t *opt = _ippFindOption(mandatory); + /* Option type */ + ipp_tag_t value_tag = opt ? opt->value_tag : IPP_TAG_NAME; + /* Value type */ + + switch (value_tag) + { + case IPP_TAG_INTEGER : + case IPP_TAG_ENUM : + ippAddInteger(request, IPP_TAG_JOB, value_tag, mandatory, + atoi(keyword)); + break; + case IPP_TAG_BOOLEAN : + ippAddBoolean(request, IPP_TAG_JOB, mandatory, + !_cups_strcasecmp(keyword, "true")); + break; + case IPP_TAG_RANGE : + { + int lower, upper; /* Range */ + + if (sscanf(keyword, "%d-%d", &lower, &upper) != 2) + lower = upper = atoi(keyword); + + ippAddRange(request, IPP_TAG_JOB, mandatory, lower, upper); + } + break; + case IPP_TAG_STRING : + ippAddOctetString(request, IPP_TAG_JOB, mandatory, keyword, + strlen(keyword)); + break; + default : + ippAddString(request, IPP_TAG_JOB, value_tag, mandatory, + NULL, keyword); + break; + } + } + } + + if ((keyword = cupsGetOption("PageSize", num_options, options)) == NULL) + keyword = cupsGetOption("media", num_options, options); + + if ((size = _ppdCacheGetSize(pc, keyword)) != NULL) + { + /* + * Add a media-col value... + */ + + media_size = ippNew(); + ippAddInteger(media_size, IPP_TAG_ZERO, IPP_TAG_INTEGER, + "x-dimension", size->width); + ippAddInteger(media_size, IPP_TAG_ZERO, IPP_TAG_INTEGER, + "y-dimension", size->length); + + media_col = ippNew(); + ippAddCollection(media_col, IPP_TAG_ZERO, "media-size", media_size); + + media_source = _ppdCacheGetSource(pc, cupsGetOption("InputSlot", + num_options, + options)); + media_type = _ppdCacheGetType(pc, cupsGetOption("MediaType", + num_options, + options)); + + for (i = 0; i < media_col_sup->num_values; i ++) + { + if (!strcmp(media_col_sup->values[i].string.text, + "media-left-margin")) + ippAddInteger(media_col, IPP_TAG_ZERO, IPP_TAG_INTEGER, + "media-left-margin", size->left); + else if (!strcmp(media_col_sup->values[i].string.text, + "media-bottom-margin")) + ippAddInteger(media_col, IPP_TAG_ZERO, IPP_TAG_INTEGER, + "media-bottom-margin", size->bottom); + else if (!strcmp(media_col_sup->values[i].string.text, + "media-right-margin")) + ippAddInteger(media_col, IPP_TAG_ZERO, IPP_TAG_INTEGER, + "media-right-margin", size->right); + else if (!strcmp(media_col_sup->values[i].string.text, + "media-top-margin")) + ippAddInteger(media_col, IPP_TAG_ZERO, IPP_TAG_INTEGER, + "media-top-margin", size->top); + else if (!strcmp(media_col_sup->values[i].string.text, + "media-source") && media_source) + ippAddString(media_col, IPP_TAG_ZERO, IPP_TAG_KEYWORD, + "media-source", NULL, media_source); + else if (!strcmp(media_col_sup->values[i].string.text, + "media-type") && media_type) + ippAddString(media_col, IPP_TAG_ZERO, IPP_TAG_KEYWORD, + "media-type", NULL, media_type); + } + + ippAddCollection(request, IPP_TAG_JOB, "media-col", media_col); + } + + if ((keyword = cupsGetOption("output-bin", num_options, + options)) == NULL) + { + if ((choice = ppdFindMarkedChoice(ppd, "OutputBin")) != NULL) + keyword = _ppdCacheGetBin(pc, choice->choice); + } + + if (keyword) + ippAddString(request, IPP_TAG_JOB, IPP_TAG_KEYWORD, "output-bin", + NULL, keyword); + + color_attr_name = print_color_mode ? "print-color-mode" : "output-mode"; + + if ((keyword = cupsGetOption("print-color-mode", num_options, + options)) != NULL) + ippAddString(request, IPP_TAG_JOB, IPP_TAG_KEYWORD, color_attr_name, + NULL, keyword); + else if ((choice = ppdFindMarkedChoice(ppd, "ColorModel")) != NULL) + { + if (!_cups_strcasecmp(choice->choice, "Gray")) + ippAddString(request, IPP_TAG_JOB, IPP_TAG_KEYWORD, + color_attr_name, NULL, "monochrome"); + else + ippAddString(request, IPP_TAG_JOB, IPP_TAG_KEYWORD, + color_attr_name, NULL, "color"); + } + + if ((keyword = cupsGetOption("print-quality", num_options, + options)) != NULL) + ippAddInteger(request, IPP_TAG_JOB, IPP_TAG_ENUM, "print-quality", + atoi(keyword)); + else if ((choice = ppdFindMarkedChoice(ppd, "cupsPrintQuality")) != NULL) + { + if (!_cups_strcasecmp(choice->choice, "draft")) + ippAddInteger(request, IPP_TAG_JOB, IPP_TAG_ENUM, "print-quality", + IPP_QUALITY_DRAFT); + else if (!_cups_strcasecmp(choice->choice, "normal")) + ippAddInteger(request, IPP_TAG_JOB, IPP_TAG_ENUM, "print-quality", + IPP_QUALITY_NORMAL); + else if (!_cups_strcasecmp(choice->choice, "high")) + ippAddInteger(request, IPP_TAG_JOB, IPP_TAG_ENUM, "print-quality", + IPP_QUALITY_HIGH); + } + + if ((keyword = cupsGetOption("sides", num_options, options)) != NULL) + ippAddString(request, IPP_TAG_JOB, IPP_TAG_KEYWORD, "sides", + NULL, keyword); + else if (pc->sides_option && + (choice = ppdFindMarkedChoice(ppd, pc->sides_option)) != NULL) + { + if (!_cups_strcasecmp(choice->choice, pc->sides_1sided)) + ippAddString(request, IPP_TAG_JOB, IPP_TAG_KEYWORD, "sides", + NULL, "one-sided"); + else if (!_cups_strcasecmp(choice->choice, pc->sides_2sided_long)) + ippAddString(request, IPP_TAG_JOB, IPP_TAG_KEYWORD, "sides", + NULL, "two-sided-long-edge"); + if (!_cups_strcasecmp(choice->choice, pc->sides_2sided_short)) + ippAddString(request, IPP_TAG_JOB, IPP_TAG_KEYWORD, "sides", + NULL, "two-sided-short-edge"); + } + + if ((keyword = cupsGetOption("multiple-document-handling", + num_options, options)) != NULL) + { + if (strstr(keyword, "uncollated")) + keyword = "false"; + else + keyword = "true"; + } + else if ((keyword = cupsGetOption("collate", num_options, + options)) == NULL) + keyword = "true"; + + if (format) + { + if (!_cups_strcasecmp(format, "image/gif") || + !_cups_strcasecmp(format, "image/jp2") || + !_cups_strcasecmp(format, "image/jpeg") || + !_cups_strcasecmp(format, "image/png") || + !_cups_strcasecmp(format, "image/tiff") || + !_cups_strncasecmp(format, "image/x-", 8)) + { + /* + * Collation makes no sense for single page image formats... + */ + + keyword = "false"; + } + else if (!_cups_strncasecmp(format, "image/", 6) || + !_cups_strcasecmp(format, "application/vnd.cups-raster")) + { + /* + * Multi-page image formats will have copies applied by the upstream + * filters... + */ + + copies = 1; + } + } + + if (doc_handling_sup) + { + if (!_cups_strcasecmp(keyword, "true")) + collate_str = "separate-documents-collated-copies"; + else + collate_str = "separate-documents-uncollated-copies"; + + for (i = 0; i < doc_handling_sup->num_values; i ++) + if (!strcmp(doc_handling_sup->values[i].string.text, collate_str)) + { + ippAddString(request, IPP_TAG_JOB, IPP_TAG_KEYWORD, + "multiple-document-handling", NULL, collate_str); + break; + } + + if (i >= doc_handling_sup->num_values) + copies = 1; + } + + /* + * Map finishing options... + */ + + num_finishings = _ppdCacheGetFinishingValues(pc, num_options, options, + (int)(sizeof(finishings) / + sizeof(finishings[0])), + finishings); + if (num_finishings > 0) + ippAddIntegers(request, IPP_TAG_JOB, IPP_TAG_ENUM, "finishings", + num_finishings, finishings); - /* - * Do the request... - */ + /* + * Map FaxOut options... + */ - if (http->version < HTTP_1_1) - httpReconnect(http); + if ((keyword = cupsGetOption("phone", num_options, options)) != NULL) + { + ipp_t *destination; /* destination collection */ + char tel_uri[1024]; /* tel: URI */ - if ((response = cupsDoRequest(http, request, resource)) != NULL) - { - report_printer_state(response, job_id); - ippDelete(response); - } -} + destination = ippNew(); + httpAssembleURI(HTTP_URI_CODING_ALL, tel_uri, sizeof(tel_uri), "tel", + NULL, NULL, 0, keyword); + ippAddString(destination, IPP_TAG_JOB, IPP_TAG_URI, "destination-uri", + NULL, tel_uri); -#ifdef HAVE_LIBZ -/* - * 'compress_files()' - Compress print files... - */ + if ((keyword = cupsGetOption("faxPrefix", num_options, + options)) != NULL && *keyword) + ippAddString(destination, IPP_TAG_JOB, IPP_TAG_TEXT, + "pre-dial-string", NULL, keyword); -static void -compress_files(int num_files, /* I - Number of files */ - char **files) /* I - Files */ -{ - int i, /* Looping var */ - fd; /* Temporary file descriptor */ - ssize_t bytes; /* Bytes read/written */ - size_t total; /* Total bytes read */ - cups_file_t *in, /* Input file */ - *out; /* Output file */ - struct stat outinfo; /* Output file information */ - char filename[1024], /* Temporary filename */ - buffer[32768]; /* Copy buffer */ - - - fprintf(stderr, "DEBUG: Compressing %d job files...\n", num_files); - for (i = 0; i < num_files; i ++) - { - if ((fd = cupsTempFd(filename, sizeof(filename))) < 0) + ippAddCollection(request, IPP_TAG_JOB, "destination-uris", destination); + ippDelete(destination); + } + } + else { - _cupsLangPrintf(stderr, - _("ERROR: Unable to create temporary compressed print " - "file: %s\n"), strerror(errno)); - exit(CUPS_BACKEND_FAILED); + /* + * When talking to another CUPS server, send all options... + */ + + fputs("DEBUG: Adding all operation/job attributes.\n", stderr); + cupsEncodeOptions2(request, num_options, options, IPP_TAG_OPERATION); + cupsEncodeOptions2(request, num_options, options, IPP_TAG_JOB); } - if ((out = cupsFileOpenFd(fd, "w9")) == NULL) + if (copies > 1 && (!pc || copies <= pc->max_copies)) + ippAddInteger(request, IPP_TAG_JOB, IPP_TAG_INTEGER, "copies", copies); + } + + fprintf(stderr, "DEBUG: IPP/%d.%d %s #%d\n", version / 10, version % 10, + ippOpString(ippGetOperation(request)), ippGetRequestId(request)); + for (group = IPP_TAG_ZERO, attr = ippFirstAttribute(request); + attr; + attr = ippNextAttribute(request)) + { + const char *name = ippGetName(attr); + + if (!name) { - _cupsLangPrintf(stderr, - _("ERROR: Unable to open temporary compressed print " - "file: %s\n"), strerror(errno)); - exit(CUPS_BACKEND_FAILED); + group = IPP_TAG_ZERO; + continue; } - if ((in = cupsFileOpen(files[i], "r")) == NULL) + if (group != ippGetGroupTag(attr)) { - _cupsLangPrintf(stderr, - _("ERROR: Unable to open print file \"%s\": %s\n"), - files[i], strerror(errno)); - cupsFileClose(out); - exit(CUPS_BACKEND_FAILED); + group = ippGetGroupTag(attr); + fprintf(stderr, "DEBUG: ---- %s ----\n", ippTagString(group)); } - total = 0; - while ((bytes = cupsFileRead(in, buffer, sizeof(buffer))) > 0) - if (cupsFileWrite(out, buffer, bytes) < bytes) - { - _cupsLangPrintf(stderr, - _("ERROR: Unable to write %d bytes to \"%s\": %s\n"), - (int)bytes, filename, strerror(errno)); - cupsFileClose(in); - cupsFileClose(out); - exit(CUPS_BACKEND_FAILED); - } - else - total += bytes; - - cupsFileClose(out); - cupsFileClose(in); + ippAttributeString(attr, buffer, sizeof(buffer)); + fprintf(stderr, "DEBUG: %s %s%s %s\n", name, + ippGetCount(attr) > 1 ? "1setOf " : "", + ippTagString(ippGetValueTag(attr)), buffer); + } - files[i] = strdup(filename); + fprintf(stderr, "DEBUG: ---- %s ----\n", ippTagString(IPP_TAG_END)); - if (!stat(filename, &outinfo)) - fprintf(stderr, - "DEBUG: File %d compressed to %.1f%% of original size, " - CUPS_LLFMT " bytes...\n", - i + 1, 100.0 * outinfo.st_size / total, - CUPS_LLCAST outinfo.st_size); - } + return (request); } -#endif /* HAVE_LIBZ */ /* @@ -1530,9 +2923,38 @@ compress_files(int num_files, /* I - Number of files */ */ static const char * /* O - Password */ -password_cb(const char *prompt) /* I - Prompt (not used) */ +password_cb(const char *prompt, /* I - Prompt (not used) */ + http_t *http, /* I - Connection */ + const char *method, /* I - Request method (not used) */ + const char *resource, /* I - Resource path (not used) */ + void *user_data) /* I - User data (not used) */ { + char def_username[HTTP_MAX_VALUE]; /* Default username */ + + + fprintf(stderr, "DEBUG: password_cb(prompt=\"%s\"), password=%p, " + "password_tries=%d\n", prompt, password, password_tries); + (void)prompt; + (void)method; + (void)resource; + (void)user_data; + + /* + * Remember that we need to authenticate... + */ + + auth_info_required = "username,password"; + + if (httpGetSubField(http, HTTP_FIELD_WWW_AUTHENTICATE, "username", + def_username)) + { + char quoted[HTTP_MAX_VALUE * 2 + 4]; + /* Quoted string */ + + fprintf(stderr, "ATTR: auth-info-default=%s,\n", + quote_string(def_username, quoted, sizeof(quoted))); + } if (password && *password && password_tries < 3) { @@ -1543,24 +2965,61 @@ password_cb(const char *prompt) /* I - Prompt (not used) */ else { /* - * If there is no password set in the device URI, return the - * "authentication required" exit code... + * Give up after 3 tries or if we don't have a password to begin with... */ - if (tmpfilename[0]) - unlink(tmpfilename); + return (NULL); + } +} + + +/* + * 'quote_string()' - Quote a string value. + */ + +static const char * /* O - Quoted string */ +quote_string(const char *s, /* I - String */ + char *q, /* I - Quoted string buffer */ + size_t qsize) /* I - Size of quoted string buffer */ +{ + char *qptr, /* Pointer into string buffer */ + *qend; /* End of string buffer */ + + + qptr = q; + qend = q + qsize - 5; -#ifdef __APPLE__ - if (pstmpname[0]) - unlink(pstmpname); -#endif /* __APPLE__ */ + if (qend < q) + { + *q = '\0'; + return (q); + } - fputs("ATTR: auth-info-required=username,password\n", stderr); + *qptr++ = '\''; + *qptr++ = '\"'; - exit(CUPS_BACKEND_AUTH_REQUIRED); + while (*s && qptr < qend) + { + if (*s == '\\' || *s == '\"' || *s == '\'') + { + if (q < (qend - 3)) + { + *qptr++ = '\\'; + *qptr++ = '\\'; + *qptr++ = '\\'; + } + else + break; + } - return (NULL); /* Eliminate compiler warning */ + *qptr++ = *s++; } + + *qptr++ = '\"'; + *qptr++ = '\''; + *qptr = '\0'; + + return (q); } @@ -1571,10 +3030,10 @@ password_cb(const char *prompt) /* I - Prompt (not used) */ static void report_attr(ipp_attribute_t *attr) /* I - Attribute */ { - int i; /* Looping var */ - char value[1024], /* Value string */ - *valptr, /* Pointer into value string */ - *attrptr; /* Pointer into attribute value */ + int i; /* Looping var */ + char value[1024], /* Value string */ + *valptr; /* Pointer into value string */ + const char *cached; /* Cached attribute */ /* @@ -1600,17 +3059,9 @@ report_attr(ipp_attribute_t *attr) /* I - Attribute */ case IPP_TAG_TEXT : case IPP_TAG_NAME : case IPP_TAG_KEYWORD : - *valptr++ = '\"'; - for (attrptr = attr->values[i].string.text; - *attrptr && valptr < (value + sizeof(value) - 10); - attrptr ++) - { - if (*attrptr == '\\' || *attrptr == '\"') - *valptr++ = '\\'; - - *valptr++ = *attrptr; - } - *valptr++ = '\"'; + quote_string(attr->values[i].string.text, valptr, + value + sizeof(value) - valptr); + valptr += strlen(valptr); break; default : @@ -1624,11 +3075,21 @@ report_attr(ipp_attribute_t *attr) /* I - Attribute */ *valptr = '\0'; - /* - * Tell the scheduler about the new values... - */ + _cupsMutexLock(&report_mutex); + + if ((cached = cupsGetOption(attr->name, num_attr_cache, + attr_cache)) == NULL || strcmp(cached, value)) + { + /* + * Tell the scheduler about the new values... + */ + + num_attr_cache = cupsAddOption(attr->name, value, num_attr_cache, + &attr_cache); + fprintf(stderr, "ATTR: %s=%s\n", attr->name, value); + } - fprintf(stderr, "ATTR: %s=%s\n", attr->name, value); + _cupsMutexUnlock(&report_mutex); } @@ -1636,294 +3097,564 @@ report_attr(ipp_attribute_t *attr) /* I - Attribute */ * 'report_printer_state()' - Report the printer state. */ -static int /* O - Number of reasons shown */ -report_printer_state(ipp_t *ipp, /* I - IPP response */ - int job_id) /* I - Current job ID */ +static void +report_printer_state(ipp_t *ipp) /* I - IPP response */ { - int i; /* Looping var */ - int count; /* Count of reasons shown... */ - ipp_attribute_t *caprm, /* com.apple.print.recoverable-message */ + ipp_attribute_t *pa, /* printer-alert */ + *pam, /* printer-alert-message */ *psm, /* printer-state-message */ *reasons, /* printer-state-reasons */ *marker; /* marker-* attributes */ - const char *reason; /* Current reason */ - const char *prefix; /* Prefix for STATE: line */ - char state[1024]; /* State string */ - int saw_caprw; /* Saw com.apple.print.recoverable-warning state */ + char value[1024], /* State/message string */ + *valptr; /* Pointer into string */ + static int ipp_supplies = -1; + /* Report supply levels? */ - if ((psm = ippFindAttribute(ipp, "printer-state-message", - IPP_TAG_TEXT)) != NULL) - fprintf(stderr, "INFO: %s\n", psm->values[0].string.text); + /* + * Report alerts and messages... + */ - if ((reasons = ippFindAttribute(ipp, "printer-state-reasons", - IPP_TAG_KEYWORD)) == NULL) - return (0); + if ((pa = ippFindAttribute(ipp, "printer-alert", IPP_TAG_TEXT)) != NULL) + report_attr(pa); - saw_caprw = 0; - state[0] = '\0'; - prefix = "STATE: "; + if ((pam = ippFindAttribute(ipp, "printer-alert-message", + IPP_TAG_TEXT)) != NULL) + report_attr(pam); - for (i = 0, count = 0; i < reasons->num_values; i ++) + if ((psm = ippFindAttribute(ipp, "printer-state-message", + IPP_TAG_TEXT)) != NULL) { - reason = reasons->values[i].string.text; + char *ptr; /* Pointer into message */ + - if (!strcmp(reason, "com.apple.print.recoverable-warning")) - saw_caprw = 1; - else if (strcmp(reason, "paused")) + strlcpy(value, "INFO: ", sizeof(value)); + for (ptr = psm->values[0].string.text, valptr = value + 6; + *ptr && valptr < (value + sizeof(value) - 6); + ptr ++) { - strlcat(state, prefix, sizeof(state)); - strlcat(state, reason, sizeof(state)); + if (*ptr < ' ' && *ptr > 0 && *ptr != '\t') + { + /* + * Substitute "" for the control character; sprintf is safe because + * we always leave 6 chars free at the end... + */ - prefix = ","; + sprintf(valptr, "<%02X>", *ptr); + valptr += 4; + } + else + *valptr++ = *ptr; } - } - if (state[0]) - fprintf(stderr, "%s\n", state); + *valptr++ = '\n'; + *valptr = '\0'; + + fputs(value, stderr); + } /* - * Relay com.apple.print.recoverable-message... + * Now report printer-state-reasons, filtering out some of the reasons we never + * want to set... */ - if ((caprm = ippFindAttribute(ipp, "com.apple.print.recoverable-message", - IPP_TAG_TEXT)) != NULL) - fprintf(stderr, "WARNING: %s: %s\n", - saw_caprw ? "recoverable" : "recovered", - caprm->values[0].string.text); + if ((reasons = ippFindAttribute(ipp, "printer-state-reasons", + IPP_TAG_KEYWORD)) == NULL) + return; + + update_reasons(reasons, NULL); /* * Relay the current marker-* attribute values... */ - if ((marker = ippFindAttribute(ipp, "marker-colors", IPP_TAG_NAME)) != NULL) - report_attr(marker); - if ((marker = ippFindAttribute(ipp, "marker-high-levels", - IPP_TAG_INTEGER)) != NULL) - report_attr(marker); - if ((marker = ippFindAttribute(ipp, "marker-levels", - IPP_TAG_INTEGER)) != NULL) - report_attr(marker); - if ((marker = ippFindAttribute(ipp, "marker-low-levels", - IPP_TAG_INTEGER)) != NULL) - report_attr(marker); - if ((marker = ippFindAttribute(ipp, "marker-message", IPP_TAG_TEXT)) != NULL) - report_attr(marker); - if ((marker = ippFindAttribute(ipp, "marker-names", IPP_TAG_NAME)) != NULL) - report_attr(marker); - if ((marker = ippFindAttribute(ipp, "marker-types", IPP_TAG_KEYWORD)) != NULL) - report_attr(marker); - - return (count); + if (ipp_supplies < 0) + { + ppd_file_t *ppd; /* PPD file */ + ppd_attr_t *ppdattr; /* Attribute in PPD file */ + + if ((ppd = ppdOpenFile(getenv("PPD"))) != NULL && + (ppdattr = ppdFindAttr(ppd, "cupsIPPSupplies", NULL)) != NULL && + ppdattr->value && _cups_strcasecmp(ppdattr->value, "true")) + ipp_supplies = 0; + else + ipp_supplies = 1; + + ppdClose(ppd); + } + + if (ipp_supplies > 0) + { + if ((marker = ippFindAttribute(ipp, "marker-colors", IPP_TAG_NAME)) != NULL) + report_attr(marker); + if ((marker = ippFindAttribute(ipp, "marker-high-levels", + IPP_TAG_INTEGER)) != NULL) + report_attr(marker); + if ((marker = ippFindAttribute(ipp, "marker-levels", + IPP_TAG_INTEGER)) != NULL) + report_attr(marker); + if ((marker = ippFindAttribute(ipp, "marker-low-levels", + IPP_TAG_INTEGER)) != NULL) + report_attr(marker); + if ((marker = ippFindAttribute(ipp, "marker-message", + IPP_TAG_TEXT)) != NULL) + report_attr(marker); + if ((marker = ippFindAttribute(ipp, "marker-names", IPP_TAG_NAME)) != NULL) + report_attr(marker); + if ((marker = ippFindAttribute(ipp, "marker-types", + IPP_TAG_KEYWORD)) != NULL) + report_attr(marker); + } } -#ifdef __APPLE__ +#if defined(HAVE_GSSAPI) && defined(HAVE_XPC) /* - * 'run_pictwps_filter()' - Convert PICT files to PostScript when printing - * remotely. + * 'run_as_user()' - Run the IPP backend as the printing user. * - * This step is required because the PICT format is not documented and - * subject to change, so developing a filter for other OS's is infeasible. - * Also, fonts required by the PICT file need to be embedded on the - * client side (which has the fonts), so we run the filter to get a - * PostScript file for printing... + * This function uses an XPC-based user agent to run the backend as the printing + * user. We need to do this in order to have access to the user's Kerberos + * credentials. */ -static int /* O - Exit status of filter */ -run_pictwps_filter(char **argv, /* I - Command-line arguments */ - const char *filename)/* I - Filename */ +static int /* O - Exit status */ +run_as_user(char *argv[], /* I - Command-line arguments */ + uid_t uid, /* I - User ID */ + const char *device_uri, /* I - Device URI */ + int fd) /* I - File to print */ { - struct stat fileinfo; /* Print file information */ - const char *ppdfile; /* PPD file for destination printer */ - int pid; /* Child process ID */ - int fd; /* Temporary file descriptor */ - int status; /* Exit status of filter */ - const char *printer; /* PRINTER env var */ - static char ppdenv[1024]; /* PPD environment variable */ + const char *auth_negotiate;/* AUTH_NEGOTIATE env var */ + xpc_connection_t conn; /* Connection to XPC service */ + xpc_object_t request; /* Request message dictionary */ + __block xpc_object_t response; /* Response message dictionary */ + dispatch_semaphore_t sem; /* Semaphore for waiting for response */ + int status = CUPS_BACKEND_FAILED; + /* Status of request */ + fprintf(stderr, "DEBUG: Running IPP backend as UID %d.\n", (int)uid); + /* - * First get the PPD file for the printer... + * Connect to the user agent for the specified UID... */ - printer = getenv("PRINTER"); - if (!printer) + conn = xpc_connection_create_mach_service(kPMPrintUIToolAgent, + dispatch_get_global_queue(0, 0), 0); + if (!conn) { - _cupsLangPuts(stderr, - _("ERROR: PRINTER environment variable not defined!\n")); - return (-1); + _cupsLangPrintFilter(stderr, "ERROR", + _("Unable to start backend process.")); + fputs("DEBUG: Unable to create connection to agent.\n", stderr); + goto cleanup; } - if ((ppdfile = cupsGetPPD(printer)) == NULL) + xpc_connection_set_event_handler(conn, + ^(xpc_object_t event) + { + xpc_type_t messageType = xpc_get_type(event); + + if (messageType == XPC_TYPE_ERROR) + { + if (event == XPC_ERROR_CONNECTION_INTERRUPTED) + fprintf(stderr, "DEBUG: Interrupted connection to service %s.\n", + xpc_connection_get_name(conn)); + else if (event == XPC_ERROR_CONNECTION_INVALID) + fprintf(stderr, "DEBUG: Connection invalid for service %s.\n", + xpc_connection_get_name(conn)); + else + fprintf(stderr, "DEBUG: Unxpected error for service %s: %s\n", + xpc_connection_get_name(conn), + xpc_dictionary_get_string(event, XPC_ERROR_KEY_DESCRIPTION)); + } + }); + xpc_connection_set_target_uid(conn, uid); + xpc_connection_resume(conn); + + /* + * Try starting the backend... + */ + + request = xpc_dictionary_create(NULL, NULL, 0); + xpc_dictionary_set_int64(request, "command", kPMStartJob); + xpc_dictionary_set_string(request, "device-uri", device_uri); + xpc_dictionary_set_string(request, "job-id", argv[1]); + xpc_dictionary_set_string(request, "user", argv[2]); + xpc_dictionary_set_string(request, "title", argv[3]); + xpc_dictionary_set_string(request, "copies", argv[4]); + xpc_dictionary_set_string(request, "options", argv[5]); + xpc_dictionary_set_string(request, "auth-info-required", + getenv("AUTH_INFO_REQUIRED")); + if ((auth_negotiate = getenv("AUTH_NEGOTIATE")) != NULL) + xpc_dictionary_set_string(request, "auth-negotiate", auth_negotiate); + xpc_dictionary_set_fd(request, "stdin", fd); + xpc_dictionary_set_fd(request, "stderr", 2); + xpc_dictionary_set_fd(request, "side-channel", CUPS_SC_FD); + + sem = dispatch_semaphore_create(0); + response = NULL; + + xpc_connection_send_message_with_reply(conn, request, + dispatch_get_global_queue(0,0), + ^(xpc_object_t reply) + { + /* Save the response and wake up */ + if (xpc_get_type(reply) + == XPC_TYPE_DICTIONARY) + response = xpc_retain(reply); + + dispatch_semaphore_signal(sem); + }); + + dispatch_semaphore_wait(sem, DISPATCH_TIME_FOREVER); + xpc_release(request); + dispatch_release(sem); + + if (response) { - _cupsLangPrintf(stderr, - _("ERROR: Unable to get PPD file for printer \"%s\" - " - "%s.\n"), printer, cupsLastErrorString()); + child_pid = xpc_dictionary_get_int64(response, "child-pid"); + + xpc_release(response); + + if (child_pid) + fprintf(stderr, "DEBUG: Child PID=%d.\n", child_pid); + else + { + _cupsLangPrintFilter(stderr, "ERROR", + _("Unable to start backend process.")); + fputs("DEBUG: No child PID.\n", stderr); + goto cleanup; + } } else { - snprintf(ppdenv, sizeof(ppdenv), "PPD=%s", ppdfile); - putenv(ppdenv); + _cupsLangPrintFilter(stderr, "ERROR", + _("Unable to start backend process.")); + fputs("DEBUG: No reply from agent.\n", stderr); + goto cleanup; } /* - * Then create a temporary file for printing... + * Then wait for the backend to finish... */ - if ((fd = cupsTempFd(pstmpname, sizeof(pstmpname))) < 0) - { - _cupsLangPrintError(_("ERROR: Unable to create temporary file")); - if (ppdfile) - unlink(ppdfile); - return (-1); - } + request = xpc_dictionary_create(NULL, NULL, 0); + xpc_dictionary_set_int64(request, "command", kPMWaitForJob); + xpc_dictionary_set_fd(request, "stderr", 2); - /* - * Get the owner of the spool file - it is owned by the user we want to run - * as... - */ + sem = dispatch_semaphore_create(0); + response = NULL; - if (argv[6]) - stat(argv[6], &fileinfo); - else + xpc_connection_send_message_with_reply(conn, request, + dispatch_get_global_queue(0,0), + ^(xpc_object_t reply) + { + /* Save the response and wake up */ + if (xpc_get_type(reply) + == XPC_TYPE_DICTIONARY) + response = xpc_retain(reply); + + dispatch_semaphore_signal(sem); + }); + + dispatch_semaphore_wait(sem, DISPATCH_TIME_FOREVER); + xpc_release(request); + dispatch_release(sem); + + if (response) { - /* - * Use the OSX defaults, as an up-stream filter created the PICT - * file... - */ + status = xpc_dictionary_get_int64(response, "status"); + + if (status == SIGTERM || status == SIGKILL || status == SIGPIPE) + { + fprintf(stderr, "DEBUG: Child terminated on signal %d.\n", status); + status = CUPS_BACKEND_FAILED; + } + else if (WIFSIGNALED(status)) + { + fprintf(stderr, "DEBUG: Child crashed on signal %d.\n", status); + status = CUPS_BACKEND_STOP; + } + else if (WIFEXITED(status)) + { + status = WEXITSTATUS(status); + fprintf(stderr, "DEBUG: Child exited with status %d.\n", status); + } - fileinfo.st_uid = 1; - fileinfo.st_gid = 80; + xpc_release(response); } + else + _cupsLangPrintFilter(stderr, "ERROR", + _("Unable to get backend exit status.")); - if (ppdfile) - chown(ppdfile, fileinfo.st_uid, fileinfo.st_gid); + cleanup: - fchown(fd, fileinfo.st_uid, fileinfo.st_gid); + if (conn) + { + xpc_connection_suspend(conn); + xpc_connection_cancel(conn); + xpc_release(conn); + } - /* - * Finally, run the filter to convert the file... - */ + return (status); +} +#endif /* HAVE_GSSAPI && HAVE_XPC */ - if ((pid = fork()) == 0) - { - /* - * Child process for pictwpstops... Redirect output of pictwpstops to a - * file... - */ - dup2(fd, 1); - close(fd); +/* + * 'sigterm_handler()' - Handle 'terminate' signals that stop the backend. + */ - if (!getuid()) - { - /* - * Change to an unpriviledged user... - */ +static void +sigterm_handler(int sig) /* I - Signal */ +{ + (void)sig; /* remove compiler warnings... */ - setgid(fileinfo.st_gid); - setuid(fileinfo.st_uid); - } + write(2, "DEBUG: Got SIGTERM.\n", 20); - execlp("pictwpstops", printer, argv[1], argv[2], argv[3], argv[4], argv[5], - filename, NULL); - _cupsLangPrintf(stderr, _("ERROR: Unable to exec pictwpstops: %s\n"), - strerror(errno)); - return (errno); +#if defined(HAVE_GSSAPI) && defined(HAVE_XPC) + if (child_pid) + { + kill(child_pid, sig); + child_pid = 0; } +#endif /* HAVE_GSSAPI && HAVE_XPC */ - close(fd); - - if (pid < 0) + if (!job_canceled) { /* - * Error! + * Flag that the job should be canceled... */ - _cupsLangPrintf(stderr, _("ERROR: Unable to fork pictwpstops: %s\n"), - strerror(errno)); - if (ppdfile) - unlink(ppdfile); - return (-1); + write(2, "DEBUG: job_canceled = 1.\n", 25); + + job_canceled = 1; + return; } /* - * Now wait for the filter to complete... + * The scheduler already tried to cancel us once, now just terminate + * after removing our temp file! */ - if (wait(&status) < 0) - { - _cupsLangPrintf(stderr, _("ERROR: Unable to wait for pictwpstops: %s\n"), - strerror(errno)); - close(fd); - if (ppdfile) - unlink(ppdfile); - return (-1); - } + if (tmpfilename[0]) + unlink(tmpfilename); + + exit(1); +} + + +/* + * 'timeout_cb()' - Handle HTTP timeouts. + */ + +static int /* O - 1 to continue, 0 to cancel */ +timeout_cb(http_t *http, /* I - Connection to server (unused) */ + void *user_data) /* I - User data (unused) */ +{ + (void)http; + (void)user_data; + + return (!job_canceled); +} + + +/* + * 'update_reasons()' - Update the printer-state-reasons values. + */ + +static void +update_reasons(ipp_attribute_t *attr, /* I - printer-state-reasons or NULL */ + const char *s) /* I - STATE: string or NULL */ +{ + char op; /* Add (+), remove (-), replace (\0) */ + cups_array_t *new_reasons; /* New reasons array */ + char *reason, /* Current reason */ + add[2048], /* Reasons added string */ + *addptr, /* Pointer into add string */ + rem[2048], /* Reasons removed string */ + *remptr; /* Pointer into remove string */ + const char *addprefix, /* Current add string prefix */ + *remprefix; /* Current remove string prefix */ + + + fprintf(stderr, "DEBUG: update_reasons(attr=%d(%s%s), s=\"%s\")\n", + attr ? attr->num_values : 0, attr ? attr->values[0].string.text : "", + attr && attr->num_values > 1 ? ",..." : "", s ? s : "(null)"); + + /* + * Create an array of new reason keyword strings... + */ - if (ppdfile) - unlink(ppdfile); + if (attr) + { + int i; /* Looping var */ - close(fd); + new_reasons = cupsArrayNew((cups_array_func_t)strcmp, NULL); + op = '\0'; - if (status) + for (i = 0; i < attr->num_values; i ++) + { + reason = attr->values[i].string.text; + + if (strcmp(reason, "none") && + strcmp(reason, "none-report") && + strcmp(reason, "paused") && + strncmp(reason, "spool-area-full", 15) && + strcmp(reason, "com.apple.print.recoverable-warning") && + strncmp(reason, "cups-", 5)) + cupsArrayAdd(new_reasons, reason); + } + } + else if (s) { - if (status >= 256) - _cupsLangPrintf(stderr, _("ERROR: pictwpstops exited with status %d!\n"), - status / 256); + if (*s == '+' || *s == '-') + op = *s++; else - _cupsLangPrintf(stderr, _("ERROR: pictwpstops exited on signal %d!\n"), - status); + op = '\0'; - return (status); + new_reasons = _cupsArrayNewStrings(s, ','); } + else + return; /* - * Return with no errors.. + * Compute the changes... */ - return (0); -} -#endif /* __APPLE__ */ + add[0] = '\0'; + addprefix = "STATE: +"; + addptr = add; + rem[0] = '\0'; + remprefix = "STATE: -"; + remptr = rem; + fprintf(stderr, "DEBUG2: op='%c', new_reasons=%d, state_reasons=%d\n", + op ? op : ' ', cupsArrayCount(new_reasons), + cupsArrayCount(state_reasons)); -/* - * 'sigterm_handler()' - Handle 'terminate' signals that stop the backend. - */ + _cupsMutexLock(&report_mutex); -static void -sigterm_handler(int sig) /* I - Signal */ -{ - (void)sig; /* remove compiler warnings... */ + if (op == '+') + { + /* + * Add reasons... + */ + + for (reason = (char *)cupsArrayFirst(new_reasons); + reason; + reason = (char *)cupsArrayNext(new_reasons)) + { + if (!cupsArrayFind(state_reasons, reason)) + { + if (!strncmp(reason, "cups-remote-", 12)) + { + /* + * If we are setting cups-remote-xxx, remove all other cups-remote-xxx + * keywords... + */ + + char *temp; /* Current reason in state_reasons */ + + cupsArraySave(state_reasons); + + for (temp = (char *)cupsArrayFirst(state_reasons); + temp; + temp = (char *)cupsArrayNext(state_reasons)) + if (!strncmp(temp, "cups-remote-", 12)) + { + snprintf(remptr, sizeof(rem) - (remptr - rem), "%s%s", remprefix, + temp); + remptr += strlen(remptr); + remprefix = ","; + + cupsArrayRemove(state_reasons, temp); + break; + } + + cupsArrayRestore(state_reasons); + } + + cupsArrayAdd(state_reasons, reason); + + snprintf(addptr, sizeof(add) - (addptr - add), "%s%s", addprefix, + reason); + addptr += strlen(addptr); + addprefix = ","; + } + } + } + else if (op == '-') + { + /* + * Remove reasons... + */ + + for (reason = (char *)cupsArrayFirst(new_reasons); + reason; + reason = (char *)cupsArrayNext(new_reasons)) + { + if (cupsArrayFind(state_reasons, reason)) + { + snprintf(remptr, sizeof(rem) - (remptr - rem), "%s%s", remprefix, + reason); + remptr += strlen(remptr); + remprefix = ","; - if (!job_cancelled) + cupsArrayRemove(state_reasons, reason); + } + } + } + else { /* - * Flag that the job should be cancelled... + * Replace reasons... */ - job_cancelled = 1; - return; + for (reason = (char *)cupsArrayFirst(state_reasons); + reason; + reason = (char *)cupsArrayNext(state_reasons)) + { + if (strncmp(reason, "cups-", 5) && !cupsArrayFind(new_reasons, reason)) + { + snprintf(remptr, sizeof(rem) - (remptr - rem), "%s%s", remprefix, + reason); + remptr += strlen(remptr); + remprefix = ","; + + cupsArrayRemove(state_reasons, reason); + } + } + + for (reason = (char *)cupsArrayFirst(new_reasons); + reason; + reason = (char *)cupsArrayNext(new_reasons)) + { + if (!cupsArrayFind(state_reasons, reason)) + { + cupsArrayAdd(state_reasons, reason); + + snprintf(addptr, sizeof(add) - (addptr - add), "%s%s", addprefix, + reason); + addptr += strlen(addptr); + addprefix = ","; + } + } } + _cupsMutexUnlock(&report_mutex); + /* - * The scheduler already tried to cancel us once, now just terminate - * after removing our temp files! + * Report changes and return... */ - if (tmpfilename[0]) - unlink(tmpfilename); - -#ifdef __APPLE__ - if (pstmpname[0]) - unlink(pstmpname); -#endif /* __APPLE__ */ - - exit(1); + if (add[0] && rem[0]) + fprintf(stderr, "%s\n%s\n", add, rem); + else if (add[0]) + fprintf(stderr, "%s\n", add); + else if (rem[0]) + fprintf(stderr, "%s\n", rem); } - /* - * End of "$Id: ipp.c 7948 2008-09-17 00:04:12Z mike $". + * End of "$Id: ipp.c 9759 2011-05-11 03:24:33Z mike $". */