X-Git-Url: http://git.ipfire.org/?a=blobdiff_plain;f=backend%2Fipp.c;h=1bbd73c3f9e7c89fa88f45981c8ce5fc31697e5e;hb=5a1d7a17697768b124bdbe8406910972e3c4df3a;hp=ed82f4d33d916b73fe9540340a6d655c2f4814eb;hpb=76cd9e37aaf496aab887d499f4917b60e91d6d25;p=thirdparty%2Fcups.git diff --git a/backend/ipp.c b/backend/ipp.c index ed82f4d33..1bbd73c3f 100644 --- a/backend/ipp.c +++ b/backend/ipp.c @@ -1,9 +1,9 @@ /* - * "$Id: ipp.c 6687 2007-07-18 19:49:45Z mike $" + * "$Id$" * - * IPP backend for the Common UNIX Printing System (CUPS). + * IPP backend for CUPS. * - * Copyright 2007 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,68 +16,184 @@ * * 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(). + * 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 -#include -#include +#include "backend-private.h" +#include #include #include -#include -#include -#include -#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); +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 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 int report_printer_state(ipp_t *ipp); + "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, + int *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); /* @@ -96,61 +212,81 @@ main(int argc, /* I - Number of command-line args */ int send_options; /* Send job options? */ int num_options; /* Number of printer options */ cups_option_t *options; /* Printer options */ - char method[255], /* Method in URI */ + 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[255], /* Name of option */ - value[255], /* Value of option */ - *ptr; /* Pointer into name or value */ + *name, /* Name of option */ + *value, /* Value of option */ + sep; /* Separator character */ + int password_tries = 0; /* Password tries */ + 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 */ + const char *content_type, /* CONTENT_TYPE environment variable */ + *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 */ - "copies-supported", - "document-format-supported", - "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() */ /* @@ -193,43 +329,110 @@ main(int argc, /* I - Number of command-line args */ else s = argv[0]; - printf("network %s \"Unknown\" \"Internet Printing Protocol (%s)\"\n", - s, s); + printf("network %s \"Unknown\" \"%s (%s)\"\n", + s, _cupsLangString(cupsLangDefault(), + _("Internet Printing Protocol")), s); return (CUPS_BACKEND_OK); } else if (argc < 6) { - fprintf(stderr, _("Usage: %s job-id user title copies options [file]\n"), - argv[0]); + _cupsLangPrintf(stderr, + _("Usage: %s job-id user title copies options [file]"), + argv[0]); return (CUPS_BACKEND_STOP); } /* - * Get the (final) content type... + * Get the device URI... */ - if ((content_type = getenv("FINAL_CONTENT_TYPE")) == NULL) - if ((content_type = getenv("CONTENT_TYPE")) == NULL) - content_type = "application/octet-stream"; + 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"; - if (!strncmp(content_type, "printer/", 8)) - content_type = "application/vnd.cups-raw"; + state_reasons = _cupsArrayNewStrings(getenv("PRINTER_STATE_REASONS"), ','); +#ifdef HAVE_GSSAPI /* - * Extract the hostname and printer name from the URI... + * For Kerberos, become the printing user (if we can) to get the credentials + * that way. */ - if (httpSeparateURI(HTTP_URI_CODING_ALL, cupsBackendDeviceURI(argv), - method, sizeof(method), username, sizeof(username), - hostname, sizeof(hostname), &port, - resource, sizeof(resource)) < HTTP_URI_OK) + if (!getuid() && (value = getenv("AUTH_UID")) != NULL && + !getenv("AUTH_PASSWORD")) { - fputs(_("ERROR: Missing device URI on command-line and no " - "DEVICE_URI environment variable!\n"), stderr); - return (CUPS_BACKEND_STOP); + 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... + */ + + if ((content_type = getenv("CONTENT_TYPE")) == NULL) + content_type = "application/octet-stream"; + + if ((final_content_type = getenv("FINAL_CONTENT_TYPE")) == NULL) + { + final_content_type = content_type; + + if (!strncmp(final_content_type, "printer/", 8)) + final_content_type = "application/vnd.cups-raw"; } - if (!strcmp(method, "https")) + /* + * Extract the hostname and printer name from the URI... + */ + + httpSeparateURI(HTTP_URI_CODING_ALL, device_uri, scheme, sizeof(scheme), + username, sizeof(username), hostname, sizeof(hostname), &port, + resource, sizeof(resource)); + + if (!port) + port = IPP_PORT; /* Default to port 631 */ + + if (!strcmp(scheme, "https") || !strcmp(scheme, "ipps")) cupsSetEncryption(HTTP_ENCRYPT_ALWAYS); else cupsSetEncryption(HTTP_ENCRYPT_IF_REQUESTED); @@ -238,8 +441,8 @@ main(int argc, /* I - Number of command-line args */ * See if there are any options... */ - compression = 0; - version = 1; + compression = NULL; + version = 20; waitjob = 1; waitprinter = 1; contimeout = 7 * 24 * 60 * 60; @@ -263,98 +466,121 @@ main(int argc, /* I - Number of command-line args */ * Get the name... */ - for (ptr = name; *optptr && *optptr != '=';) - if (ptr < (name + sizeof(name) - 1)) - *ptr++ = *optptr++; - *ptr = '\0'; + name = optptr; + + while (*optptr && *optptr != '=' && *optptr != '+' && *optptr != '&') + optptr ++; + + if ((sep = *optptr) != '\0') + *optptr++ = '\0'; - if (*optptr == '=') + if (sep == '=') { /* * Get the value... */ - optptr ++; - - for (ptr = value; *optptr && *optptr != '+' && *optptr != '&';) - if (ptr < (value + sizeof(value) - 1)) - *ptr++ = *optptr++; - *ptr = '\0'; + value = optptr; - if (*optptr == '+' || *optptr == '&') + while (*optptr && *optptr != '+' && *optptr != '&') optptr ++; + + if (*optptr) + *optptr++ = '\0'; } else - value[0] = '\0'; + value = (char *)""; /* * 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 { - fprintf(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 = 0; + version = 10; else if (!strcmp(value, "1.1")) - version = 1; + version = 11; + else if (!strcmp(value, "2.0")) + version = 20; + else if (!strcmp(value, "2.1")) + version = 21; + else if (!strcmp(value, "2.2")) + version = 22; else { - fprintf(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... @@ -369,8 +595,9 @@ main(int argc, /* I - Number of command-line args */ * Unknown option... */ - fprintf(stderr, _("ERROR: Unknown option \"%s\" with value \"%s\"!\n"), - name, value); + _cupsLangPrintFilter(stderr, "ERROR", + _("Unknown option \"%s\" with value \"%s\"."), + name, value); } } } @@ -383,50 +610,13 @@ main(int argc, /* I - Number of command-line args */ if (argc == 6) { - /* - * Copy stdin to a temporary file... - */ - - int fd; /* File descriptor */ - cups_file_t *fp; /* Temporary file */ - char buffer[8192]; /* Buffer for copying */ - int bytes; /* Number of bytes read */ - - - if ((fd = cupsTempFd(tmpfilename, sizeof(tmpfilename))) < 0) - { - perror("ERROR: unable to create temporary file"); - return (CUPS_BACKEND_FAILED); - } - - if ((fp = cupsFileOpenFd(fd, compression ? "w9" : "w")) == NULL) - { - perror("ERROR: unable to open temporary file"); - close(fd); - unlink(tmpfilename); - return (CUPS_BACKEND_FAILED); - } - - while ((bytes = fread(buffer, 1, sizeof(buffer), stdin)) > 0) - if (cupsFileWrite(fp, buffer, bytes) < bytes) - { - perror("ERROR: unable to write to temporary file"); - cupsFileClose(fp); - unlink(tmpfilename); - return (CUPS_BACKEND_FAILED); - } - - cupsFileClose(fp); - - /* - * Point to the single file from stdin... - */ + 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); - filename = tmpfilename; - files = &filename; - num_files = 1; - - send_options = 0; + fputs("DEBUG: Sending stdin for job...\n", stderr); } else { @@ -434,29 +624,23 @@ main(int argc, /* I - Number of command-line args */ * Point to the files on the command-line... */ - num_files = argc - 6; - files = argv + 6; - - send_options = strncasecmp(content_type, "application/vnd.cups-", 21) != 0; + num_files = argc - 6; + 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((cups_password_cb2_t)password_cb, &password_tries); if (username[0]) { /* - * Use authenticaion information in the device URI... + * Use authentication information in the device URI... */ if ((password = strchr(username, ':')) != NULL) @@ -464,36 +648,100 @@ main(int argc, /* I - Number of command-line args */ cupsSetUser(username); } - else if (!getuid()) + else { /* * Try loading authentication information from the environment. */ - if ((ptr = getenv("AUTH_USERNAME")) != NULL) + 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... */ - delay = 5; - recoverable = 0; - start_time = time(NULL); + 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... + */ + + 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, _("INFO: Connecting to %s on port %d...\n"), - hostname, port); + fprintf(stderr, "DEBUG: Connecting to %s:%d\n", hostname, port); + _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) @@ -505,11 +753,9 @@ main(int argc, /* I - Number of command-line args */ * available printer in the class. */ - fputs(_("INFO: Unable to contact printer, queuing on next " - "printer in class...\n"), stderr); - - if (argc == 6 || strcmp(filename, argv[6])) - unlink(filename); + _cupsLangPrintFilter(stderr, "INFO", + _("Unable to contact printer, queuing on next " + "printer in class.")); /* * Sleep 5 seconds to keep the job from requeuing too rapidly... @@ -517,74 +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) { - fputs(_("ERROR: Printer not responding!\n"), stderr); + _cupsLangPrintFilter(stderr, "ERROR", + _("The printer is not responding.")); + update_reasons(NULL, "-connecting-to-device"); return (CUPS_BACKEND_FAILED); } - recoverable = 1; - - fprintf(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) - { - fprintf(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)); - fputs(_("ERROR: recoverable: Unable to connect to printer; will " - "retry in 30 seconds...\n"), stderr); + _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) - { - if (argc == 6 || strcmp(filename, argv[6])) - unlink(filename); + 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); - fprintf(stderr, _("INFO: Connected to %s...\n"), hostname); + update_reasons(NULL, "-connecting-to-device"); + _cupsLangPrintFilter(stderr, "INFO", _("Connected to printer.")); -#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)); + 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 @@ -592,26 +839,40 @@ main(int argc, /* I - Number of command-line args */ * might contain username:password information... */ - snprintf(uri, sizeof(uri), "%s://%s:%d%s", method, hostname, port, resource); + httpAssembleURI(HTTP_URI_CODING_ALL, uri, sizeof(uri), scheme, NULL, hostname, + port, resource); /* * 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 { + /* + * Check for side-channel requests... + */ + + backendCheckSideChannel(snmp_fd, http->hostaddr); + /* * Build the IPP request... */ request = ippNewRequest(IPP_GET_PRINTER_ATTRIBUTES); - request->request.op.version[1] = version; + request->request.op.version[0] = version / 10; + request->request.op.version[1] = version % 10; ippAddString(request, IPP_TAG_OPERATION, IPP_TAG_URI, "printer-uri", NULL, uri); @@ -626,85 +887,218 @@ main(int argc, /* I - Number of command-line args */ fputs("DEBUG: Getting supported attributes...\n", stderr); - if ((supported = cupsDoRequest(http, request, resource)) == NULL) - ipp_status = cupsLastError(); - else - ipp_status = supported->request.status.status_code; + if (http->version < HTTP_1_1) + { + 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 (ipp_status > IPP_OK_CONFLICT) + supported = cupsDoRequest(http, request, resource); + ipp_status = cupsLastError(); + + 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) { - fputs(_("ERROR: Printer not responding!\n"), stderr); + _cupsLangPrintFilter(stderr, "ERROR", + _("The printer is not responding.")); return (CUPS_BACKEND_FAILED); } - recoverable = 1; - - fprintf(stderr, - _("WARNING: recoverable: Network host \'%s\' is busy; will " - "retry in %d seconds...\n"), - hostname, delay); + _cupsLangPrintFilter(stderr, "INFO", _("The printer is in use.")); 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 == 1) + ipp_status == IPP_VERSION_NOT_SUPPORTED) && version > 10) { /* - * Switch to IPP/1.0... + * Switch to IPP/1.1 or IPP/1.0... */ - fputs(_("INFO: Printer does not support IPP/1.1, trying IPP/1.0...\n"), - stderr); - version = 0; + 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) { - fputs(_("ERROR: Destination printer does not exist!\n"), stderr); + _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) { - fprintf(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); @@ -713,9 +1107,101 @@ main(int argc, /* I - Number of command-line args */ format_sup->values[i].string.text); } + 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); + } + + print_color_mode = ippFindAttribute(supported, + "print-color-mode-supported", + IPP_TAG_KEYWORD) != NULL; + + if ((operations_sup = ippFindAttribute(supported, "operations-supported", + IPP_TAG_ENUM)) != NULL) + { + 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 (ipp_status > IPP_OK_CONFLICT); + 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 @@ -742,15 +1228,13 @@ main(int argc, /* I - Number of command-line args */ * available printer in the class. */ - fputs(_("INFO: Unable to contact printer, queuing on next " - "printer in class...\n"), stderr); + _cupsLangPrintFilter(stderr, "INFO", + _("Unable to contact printer, queuing on next " + "printer in class.")); ippDelete(supported); httpClose(http); - if (argc == 6 || strcmp(filename, argv[6])) - unlink(filename); - /* * Sleep 5 seconds to keep the job from requeuing too rapidly... */ @@ -761,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... */ @@ -780,213 +1252,442 @@ 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) { - /* - * Build the IPP request... - */ + num_options = cupsParseOptions(argv[5], 0, &options); - if (job_cancelled) - break; + if (!cups_version && media_col_sup) + { + /* + * Load the PPD file and generate PWG attribute mapping information... + */ - if (num_files > 1) - request = ippNewRequest(IPP_CREATE_JOB); - else - request = ippNewRequest(IPP_PRINT_JOB); + ppd = ppdOpenFile(getenv("PPD")); + pc = _ppdCacheCreateWithPPD(ppd); - request->request.op.version[1] = version; + ppdMarkDefaults(ppd); + cupsMarkOptions(ppd, num_options, options); + } + } + else + num_options = 0; - ippAddString(request, IPP_TAG_OPERATION, IPP_TAG_URI, "printer-uri", - NULL, uri); + document_format = NULL; - fprintf(stderr, "DEBUG: printer-uri = \"%s\"\n", uri); + 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; + } - if (argv[2][0]) - ippAddString(request, IPP_TAG_OPERATION, IPP_TAG_NAME, - "requesting-user-name", NULL, argv[2]); + 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: requesting-user-name = \"%s\"\n", argv[2]); + fprintf(stderr, "DEBUG: final_content_type=\"%s\", document_format=\"%s\"\n", + final_content_type, document_format ? document_format : "(null)"); - /* - * 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)... - */ + /* + * 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!) + */ - if (argv[3][0] && copies_sup) - ippAddString(request, IPP_TAG_OPERATION, IPP_TAG_NAME, "job-name", NULL, - argv[3]); + 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); + } - fprintf(stderr, "DEBUG: job-name = \"%s\"\n", argv[3]); + _cupsLangPrintFilter(stderr, "INFO", _("Copying print data.")); -#ifdef HAVE_LIBZ - if (compression) - ippAddString(request, IPP_TAG_OPERATION, IPP_TAG_KEYWORD, - "compression", NULL, "gzip"); -#endif /* HAVE_LIBZ */ + if ((compatsize = write(fd, buffer, bytes)) < 0) + { + perror("DEBUG: Unable to write temporary file"); + return (CUPS_BACKEND_FAILED); + } - /* - * Handle options on the command-line... - */ + if ((bytes = backendRunLoop(-1, fd, snmp_fd, &(addrlist->addr), 0, 0, + backendNetworkSideCB)) < 0) + return (CUPS_BACKEND_FAILED); - options = NULL; - num_options = cupsParseOptions(argv[5], 0, &options); + compatsize += bytes; -#ifdef __APPLE__ - if (content_type != NULL && - !strcasecmp(content_type, "application/pictwps") && num_files == 1) - { - if (format_sup != NULL) - { - for (i = 0; i < format_sup->num_values; i ++) - if (!strcasecmp(content_type, format_sup->values[i].string.text)) - break; - } + close(fd); - 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... - */ + compatfile = tmpfilename; + files = &compatfile; + num_files = 1; + } + else if (http->version < HTTP_1_1 && num_files == 1) + { + struct stat fileinfo; /* File information */ - if (run_pictwps_filter(argv, filename)) - return (CUPS_BACKEND_FAILED); + if (!stat(files[0], &fileinfo)) + compatsize = fileinfo.st_size; + } - filename = pstmpname; + /* + * 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... + */ - /* - * Change the MIME type to application/postscript and change the - * number of copies to 1... - */ + if (version == 10) + create_job = send_document = 0; - content_type = "application/postscript"; - copies = 1; - copies_remaining = 1; - send_options = 0; - } - } -#endif /* __APPLE__ */ + /* + * Start monitoring the printer in the background... + */ - if (content_type != NULL && format_sup != NULL) - { - for (i = 0; i < format_sup->num_values; i ++) - if (!strcasecmp(content_type, format_sup->values[i].string.text)) - break; + 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; + } + + _cupsThreadCreate((_cups_thread_func_t)monitor_printer, &monitor); + + /* + * Validate access to the printer... + */ - if (i < format_sup->num_values) - ippAddString(request, IPP_TAG_OPERATION, IPP_TAG_MIMETYPE, - "document-format", NULL, content_type); + 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); + + response = cupsDoRequest(http, request, resource); + + 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 > 0 && 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; + } + + /* + * Then issue the print-job request... + */ - cupsFreeOptions(num_options, options); + 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); + + /* + * Build the IPP job creation request... + */ + + 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) { - fputs(_("INFO: Printer busy; will retry in 10 seconds...\n"), stderr); + _cupsLangPrintFilter(stderr, "INFO", _("The printer is in use.")); 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 ((ipp_status == IPP_BAD_REQUEST || - ipp_status == IPP_VERSION_NOT_SUPPORTED) && version == 1) + else if (ipp_status == IPP_STATUS_ERROR_JOB_CANCELED || + ipp_status == IPP_STATUS_ERROR_NOT_AUTHORIZED || + ipp_status == IPP_STATUS_ERROR_ATTRIBUTES_OR_VALUES || + 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 { /* - * Switch to IPP/1.0... + * Update auth-info-required as needed... */ - fputs(_("INFO: Printer does not support IPP/1.1, trying IPP/1.0...\n"), - stderr); - version = 0; - httpReconnect(http); + _cupsLangPrintFilter(stderr, "ERROR", + _("Print job was not accepted.")); + + 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 (!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 - fprintf(stderr, _("ERROR: Print file was not accepted (%s)!\n"), - cupsLastErrorString()); } else if ((job_id_attr = ippFindAttribute(response, "job-id", IPP_TAG_INTEGER)) == NULL) { - fputs(_("NOTICE: Print file accepted - job ID unknown.\n"), stderr); + 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; - fprintf(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 ++) { - request = ippNewRequest(IPP_SEND_DOCUMENT); + /* + * Check for side-channel requests... + */ + + backendCheckSideChannel(snmp_fd, http->hostaddr); + + /* + * Send the next file in the job... + */ - request->request.op.version[1] = version; + request = ippNewRequest(IPP_SEND_DOCUMENT); + request->request.op.version[0] = version / 10; + request->request.op.version[1] = version % 10; ippAddString(request, IPP_TAG_OPERATION, IPP_TAG_URI, "printer-uri", NULL, uri); @@ -998,22 +1699,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) - ippAddBoolean(request, IPP_TAG_OPERATION, "last-document", 1); + ippAddBoolean(request, IPP_TAG_OPERATION, "last-document", + (i + 1) >= num_files); + + 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))); - ippAddString(request, IPP_TAG_OPERATION, IPP_TAG_MIMETYPE, - "document-format", NULL, content_type); + 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(); - fprintf(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; + } } } @@ -1023,8 +1790,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) - break; + { + 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 --; @@ -1032,19 +1869,35 @@ 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; - fputs(_("INFO: Waiting for job to complete...\n"), stderr); + _cupsLangPrintFilter(stderr, "INFO", _("Waiting for job to complete.")); - for (; !job_cancelled;) + for (delay = _cupsNextDelay(0, &prev_delay); !job_canceled;) { + /* + * Check for side-channel requests... + */ + + backendCheckSideChannel(snmp_fd, http->hostaddr); + + /* + * Check printer state... + */ + + check_printer_state(http, uri, resource, argv[2], version); + + if (cupsLastError() <= IPP_OK_CONFLICT) + password_tries = 0; + /* * Build an IPP_GET_JOB_ATTRIBUTES request... */ request = ippNewRequest(IPP_GET_JOB_ATTRIBUTES); - request->request.op.version[1] = version; + request->request.op.version[0] = version / 10; + request->request.op.version[1] = version % 10; ippAddString(request, IPP_TAG_OPERATION, IPP_TAG_URI, "printer-uri", NULL, uri); @@ -1064,35 +1917,49 @@ main(int argc, /* I - Number of command-line args */ * Do the request... */ - if (!copies_sup) - 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); - - fprintf(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) @@ -1100,37 +1967,64 @@ main(int argc, /* I - Number of command-line args */ if ((job_state = ippFindAttribute(response, "job-state", IPP_TAG_ENUM)) != NULL) { - /* - * Stop polling if the job is finished or pending-held... + /* + * Reflect the remote job state in the local queue... */ - 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); + 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) + { 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... + * Wait before polling again... */ - check_printer_state(http, uri, resource, argv[2], version); - - /* - * Wait 10 seconds before polling again... - */ + sleep(delay); - sleep(10); + delay = _cupsNextDelay(delay, &prev_delay); } } @@ -1138,7 +2032,7 @@ 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); /* @@ -1147,10 +2041,38 @@ main(int argc, /* I - Number of command-line args */ check_printer_state(http, uri, resource, argv[2], version); + if (cupsLastError() <= IPP_OK_CONFLICT) + password_tries = 0; + + /* + * Collect the final page count as needed... + */ + + 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); @@ -1162,35 +2084,55 @@ 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) + return (CUPS_BACKEND_AUTH_REQUIRED); + else if (ipp_status == IPP_STATUS_ERROR_CUPS_ACCOUNT_LIMIT_REACHED || + ipp_status == IPP_STATUS_ERROR_CUPS_ACCOUNT_INFO_NEEDED || + ipp_status == IPP_STATUS_ERROR_CUPS_ACCOUNT_CLOSED || + ipp_status == IPP_STATUS_ERROR_CUPS_ACCOUNT_AUTHORIZATION_FAILED) + return (CUPS_BACKEND_HOLD); + 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_STATUS_ERROR_ATTRIBUTES_OR_VALUES || + ipp_status == IPP_DOCUMENT_FORMAT || job_canceled < 0) { - /* - * Authorization failures here mean that we need Kerberos. Username + - * password authentication is handled in the password_cb function. - */ + 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 if (ipp_status == IPP_STATUS_ERROR_ATTRIBUTES_OR_VALUES) + _cupsLangPrintFilter(stderr, "ERROR", + _("Printer cannot print with supplied options.")); + else + _cupsLangPrintFilter(stderr, "ERROR", _("Print job canceled at printer.")); - fputs("ATTR: auth-info-required=negotiate\n", stderr); - return (CUPS_BACKEND_AUTH_REQUIRED); + return (CUPS_BACKEND_CANCEL); } - else if (ipp_status > IPP_OK_CONFLICT) - return (CUPS_BACKEND_FAILED); + else if (ipp_status > IPP_OK_CONFLICT && ipp_status != IPP_ERROR_JOB_CANCELED) + return (CUPS_BACKEND_RETRY_CURRENT); else return (CUPS_BACKEND_OK); } @@ -1211,10 +2153,11 @@ cancel_job(http_t *http, /* I - HTTP connection */ ipp_t *request; /* Cancel-Job request */ - fputs(_("INFO: Canceling print job...\n"), stderr); + _cupsLangPrintFilter(stderr, "INFO", _("Canceling print job.")); request = ippNewRequest(IPP_CANCEL_JOB); - request->request.op.version[1] = version; + request->request.op.version[0] = version / 10; + request->request.op.version[1] = version % 10; ippAddString(request, IPP_TAG_OPERATION, IPP_TAG_URI, "printer-uri", NULL, uri); @@ -1231,503 +2174,1510 @@ cancel_job(http_t *http, /* I - HTTP connection */ ippDelete(cupsDoRequest(http, request, resource)); if (cupsLastError() > IPP_OK_CONFLICT) - fprintf(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 */ -{ - ipp_t *request, /* IPP request */ - *response; /* IPP response */ - static const char * const attrs[] = /* Attributes we want */ - { - "printer-state-message", - "printer-state-reasons" - }; + { + 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); - request->request.op.version[1] = version; + request->request.op.version[0] = version / 10; + 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); - - /* - * Do the request... - */ + "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()); + + /* + * Return the printer-state value... + */ + + return (printer_state); } -#ifdef HAVE_LIBZ /* - * 'compress_files()' - Compress print files... + * 'monitor_printer()' - Monitor the printer state. */ -static void -compress_files(int num_files, /* I - Number of files */ - char **files) /* I - Files */ +static void * /* O - Thread exit code */ +monitor_printer( + _cups_monitor_t *monitor) /* I - Monitoring data */ { - 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) - { - fprintf(stderr, - _("ERROR: Unable to create temporary compressed print file: " - "%s\n"), - strerror(errno)); - exit(CUPS_BACKEND_FAILED); - } - - if ((out = cupsFileOpenFd(fd, "w9")) == NULL) - { - fprintf(stderr, - _("ERROR: Unable to open temporary compressed print file: %s\n"), - strerror(errno)); - exit(CUPS_BACKEND_FAILED); - } - - if ((in = cupsFileOpen(files[i], "r")) == NULL) - { - fprintf(stderr, _("ERROR: Unable to open print file \"%s\": %s\n"), - files[i], strerror(errno)); - cupsFileClose(out); - exit(CUPS_BACKEND_FAILED); - } - - total = 0; - while ((bytes = cupsFileRead(in, buffer, sizeof(buffer))) > 0) - if (cupsFileWrite(out, buffer, bytes) < bytes) - { - fprintf(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; + 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 */ + int password_tries = 0; /* Password tries */ - cupsFileClose(out); - cupsFileClose(in); - files[i] = strdup(filename); + /* + * Make a copy of the printer connection... + */ - 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); - } -} -#endif /* HAVE_LIBZ */ + 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((cups_password_cb2_t)password_cb, &password_tries); -/* - * 'password_cb()' - Disable the password prompt for cupsDoFileRequest(). - */ + /* + * Loop until the job is canceled, aborted, or completed. + */ -static const char * /* O - Password */ -password_cb(const char *prompt) /* I - Prompt (not used) */ -{ - (void)prompt; + delay = _cupsNextDelay(0, &prev_delay); - if (password && password_tries < 3) - { - password_tries ++; + monitor->job_reasons = 0; - return (password); - } - else + while (monitor->job_state < IPP_JOB_CANCELED && !job_canceled) { /* - * If there is no password set in the device URI, return the - * "authentication required" exit code... + * Reconnect to the printer... */ - if (tmpfilename[0]) - unlink(tmpfilename); + if (!httpReconnect(http)) + { + /* + * Connected, so check on the printer state... + */ -#ifdef __APPLE__ - if (pstmpname[0]) - unlink(pstmpname); -#endif /* __APPLE__ */ + monitor->printer_state = check_printer_state(http, monitor->uri, + monitor->resource, + monitor->user, + monitor->version); + if (cupsLastError() <= IPP_OK_CONFLICT) + password_tries = 0; - fputs("ATTR: auth-info-required=username,password\n", stderr); + /* + * Check the status of the job itself... + */ - exit(CUPS_BACKEND_AUTH_REQUIRED); + 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; - return (NULL); /* Eliminate compiler warning */ - } -} + 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); -/* - * 'report_printer_state()' - Report the printer state. - */ + ippAddStrings(request, IPP_TAG_OPERATION, IPP_TAG_KEYWORD, + "requested-attributes", + (int)(sizeof(jattrs) / sizeof(jattrs[0])), NULL, jattrs); -static int /* O - Number of reasons shown */ -report_printer_state(ipp_t *ipp) /* I - IPP response */ -{ - int i; /* Looping var */ - int count; /* Count of reasons shown... */ - ipp_attribute_t *psm, /* pritner-state-message */ - *reasons; /* printer-state-reasons */ - const char *reason; /* Current reason */ - const char *message; /* Message to show */ - char unknown[1024]; /* Unknown message string */ - const char *prefix; /* Prefix for STATE: line */ - char state[1024]; /* State string */ + /* + * Do the request... + */ + response = cupsDoRequest(http, request, monitor->resource); - if ((psm = ippFindAttribute(ipp, "printer-state-message", - IPP_TAG_TEXT)) != NULL) - fprintf(stderr, "INFO: %s\n", psm->values[0].string.text); + fprintf(stderr, "DEBUG: (monitor) %s: %s (%s)\n", ippOpString(job_op), + ippErrorString(cupsLastError()), cupsLastErrorString()); - if ((reasons = ippFindAttribute(ipp, "printer-state-reasons", - IPP_TAG_KEYWORD)) == NULL) - return (0); + if (cupsLastError() <= IPP_OK_CONFLICT) + password_tries = 0; - state[0] = '\0'; - prefix = "STATE: "; + 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; + } + } - for (i = 0, count = 0; i < reasons->num_values; i ++) - { - reason = reasons->values[i].string.text; - - strlcat(state, prefix, sizeof(state)); - strlcat(state, reason, sizeof(state)); - - prefix = ","; - message = ""; - - if (!strncmp(reason, "media-needed", 12)) - message = _("Media tray needs to be filled."); - else if (!strncmp(reason, "media-jam", 9)) - message = _("Media jam!"); - else if (!strncmp(reason, "moving-to-paused", 16) || - !strncmp(reason, "paused", 6) || - !strncmp(reason, "shutdown", 8)) - message = _("Printer off-line."); - else if (!strncmp(reason, "toner-low", 9)) - message = _("Toner low."); - else if (!strncmp(reason, "toner-empty", 11)) - message = _("Out of toner!"); - else if (!strncmp(reason, "cover-open", 10)) - message = _("Cover open."); - else if (!strncmp(reason, "interlock-open", 14)) - message = _("Interlock open."); - else if (!strncmp(reason, "door-open", 9)) - message = _("Door open."); - else if (!strncmp(reason, "input-tray-missing", 18)) - message = _("Media tray missing!"); - else if (!strncmp(reason, "media-low", 9)) - message = _("Media tray almost empty."); - else if (!strncmp(reason, "media-empty", 11)) - message = _("Media tray empty!"); - else if (!strncmp(reason, "output-tray-missing", 19)) - message = _("Output tray missing!"); - else if (!strncmp(reason, "output-area-almost-full", 23)) - message = _("Output bin almost full."); - else if (!strncmp(reason, "output-area-full", 16)) - message = _("Output bin full!"); - else if (!strncmp(reason, "marker-supply-low", 17)) - message = _("Ink/toner almost empty."); - else if (!strncmp(reason, "marker-supply-empty", 19)) - message = _("Ink/toner empty!"); - else if (!strncmp(reason, "marker-waste-almost-full", 24)) - message = _("Ink/toner waste bin almost full."); - else if (!strncmp(reason, "marker-waste-full", 17)) - message = _("Ink/toner waste bin full!"); - else if (!strncmp(reason, "fuser-over-temp", 15)) - message = _("Fuser temperature high!"); - else if (!strncmp(reason, "fuser-under-temp", 16)) - message = _("Fuser temperature low!"); - else if (!strncmp(reason, "opc-near-eol", 12)) - message = _("OPC almost at end-of-life."); - else if (!strncmp(reason, "opc-life-over", 13)) - message = _("OPC at end-of-life!"); - else if (!strncmp(reason, "developer-low", 13)) - message = _("Developer almost empty."); - else if (!strncmp(reason, "developer-empty", 15)) - message = _("Developer empty!"); - else if (strstr(reason, "error") != NULL) - { - message = unknown; + 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; + } - snprintf(unknown, sizeof(unknown), _("Unknown printer error (%s)!"), - reason); - } + 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; + } + } - if (message[0]) - { - count ++; - if (strstr(reasons->values[i].string.text, "error")) - fprintf(stderr, "ERROR: %s\n", message); - else if (strstr(reasons->values[i].string.text, "warning")) - fprintf(stderr, "WARNING: %s\n", message); - else - fprintf(stderr, "INFO: %s\n", message); + 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); } - fprintf(stderr, "%s\n", state); + /* + * 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 (count); + return (NULL); } -#ifdef __APPLE__ /* - * 'run_pictwps_filter()' - Convert PICT files to PostScript when printing - * remotely. - * - * 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... + * 'new_request()' - Create a new print creation or validation request. */ -static int /* O - Exit status of filter */ -run_pictwps_filter(char **argv, /* I - Command-line arguments */ - const char *filename)/* I - Filename */ +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 */ { - 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 */ + 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 */ /* - * First get the PPD file for the printer... + * Create the IPP request... */ - printer = getenv("PRINTER"); - if (!printer) - { - fputs(_("ERROR: PRINTER environment variable not defined!\n"), stderr); - return (-1); - } + request = ippNewRequest(op); + request->request.op.version[0] = version / 10; + request->request.op.version[1] = version % 10; - if ((ppdfile = cupsGetPPD(printer)) == NULL) - { - fprintf(stderr, - _("ERROR: Unable to get PPD file for printer \"%s\" - %s.\n"), - printer, cupsLastErrorString()); - } - else - { - snprintf(ppdenv, sizeof(ppdenv), "PPD=%s", ppdfile); - putenv(ppdenv); - } + fprintf(stderr, "DEBUG: %s IPP/%d.%d\n", + ippOpString(request->request.op.operation_id), + request->request.op.version[0], + request->request.op.version[1]); /* - * Then create a temporary file for printing... + * Add standard attributes... */ - if ((fd = cupsTempFd(pstmpname, sizeof(pstmpname))) < 0) + ippAddString(request, IPP_TAG_OPERATION, IPP_TAG_URI, "printer-uri", + NULL, uri); + fprintf(stderr, "DEBUG: printer-uri=\"%s\"\n", uri); + + if (user && *user) { - fprintf(stderr, _("ERROR: Unable to create temporary file - %s.\n"), - strerror(errno)); - if (ppdfile) - unlink(ppdfile); - return (-1); + ippAddString(request, IPP_TAG_OPERATION, IPP_TAG_NAME, + "requesting-user-name", NULL, user); + fprintf(stderr, "DEBUG: requesting-user-name=\"%s\"\n", user); } - /* - * Get the owner of the spool file - it is owned by the user we want to run - * as... - */ - - if (argv[6]) - stat(argv[6], &fileinfo); - else + if (title && *title) { - /* - * Use the OSX defaults, as an up-stream filter created the PICT - * file... - */ - - fileinfo.st_uid = 1; - fileinfo.st_gid = 80; + ippAddString(request, IPP_TAG_OPERATION, IPP_TAG_NAME, "job-name", NULL, + title); + fprintf(stderr, "DEBUG: job-name=\"%s\"\n", title); } - if (ppdfile) - chown(ppdfile, fileinfo.st_uid, fileinfo.st_gid); + 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); + } - fchown(fd, fileinfo.st_uid, fileinfo.st_gid); +#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 */ /* - * Finally, run the filter to convert the file... + * Handle options on the command-line... */ - if ((pid = fork()) == 0) + if (num_options > 0) { - /* - * Child process for pictwpstops... Redirect output of pictwpstops to a - * file... - */ - - close(1); - dup(fd); - close(fd); - - if (!getuid()) + if (pc) { + int num_finishings = 0, /* Number of finishing values */ + finishings[10]; /* Finishing enum values */ + ppd_choice_t *choice; /* Marked choice */ + /* - * Change to an unpriviledged user... + * Send standard IPP attributes... */ - setgid(fileinfo.st_gid); - setuid(fileinfo.st_uid); - } + fputs("DEBUG: Adding standard IPP operation/job attributes.\n", stderr); - execlp("pictwpstops", printer, argv[1], argv[2], argv[3], argv[4], argv[5], - filename, NULL); - fprintf(stderr, _("ERROR: Unable to exec pictwpstops: %s\n"), - strerror(errno)); - return (errno); - } + if (pc->password && + (keyword = cupsGetOption("job-password", num_options, + options)) != NULL) + { + ippAddOctetString(request, IPP_TAG_OPERATION, "job-password", + keyword, strlen(keyword)); - close(fd); + if ((keyword = cupsGetOption("job-password-encryption", num_options, + options)) == NULL) + keyword = "none"; - if (pid < 0) - { - /* - * Error! - */ + ippAddString(request, IPP_TAG_OPERATION, IPP_TAG_KEYWORD, + "job-password-encryption", NULL, keyword); + } - fprintf(stderr, _("ERROR: Unable to fork pictwpstops: %s\n"), - strerror(errno)); - unlink(filename); - if (ppdfile) - unlink(ppdfile); - return (-1); - } + if (pc->account_id) + { + if ((keyword = cupsGetOption("job-account-id", num_options, + options)) == NULL) + keyword = cupsGetOption("job-billing", num_options, options); - /* - * Now wait for the filter to complete... - */ + if (keyword) + ippAddString(request, IPP_TAG_JOB, IPP_TAG_NAME, "job-account-id", + NULL, keyword); + } - if (wait(&status) < 0) - { - fprintf(stderr, _("ERROR: Unable to wait for pictwpstops: %s\n"), - strerror(errno)); - close(fd); - unlink(filename); - if (ppdfile) - unlink(ppdfile); - return (-1); - } + if (pc->accounting_user_id) + { + if ((keyword = cupsGetOption("job-accounting-user-id", num_options, + options)) == NULL) + keyword = user; - if (ppdfile) - unlink(ppdfile); + if (keyword) + ippAddString(request, IPP_TAG_JOB, IPP_TAG_NAME, + "job-accounting-user-id", NULL, keyword); + } - close(fd); + 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 (status) - { - if (status >= 256) - fprintf(stderr, _("ERROR: pictwpstops exited with status %d!\n"), - status / 256); - else - fprintf(stderr, _("ERROR: pictwpstops exited on signal %d!\n"), - status); + if ((keyword = cupsGetOption("PageSize", num_options, options)) == NULL) + keyword = cupsGetOption("media", num_options, options); - unlink(filename); - return (status); - } + if ((size = _ppdCacheGetSize(pc, keyword)) != NULL) + { + /* + * Add a media-col value... + */ - /* - * Return with no errors.. - */ + 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); - return (0); -} -#endif /* __APPLE__ */ + 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)); -/* - * 'sigterm_handler()' - Handle 'terminate' signals that stop the backend. - */ + 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); + } -static void -sigterm_handler(int sig) /* I - Signal */ -{ - (void)sig; /* remove compiler warnings... */ + ippAddCollection(request, IPP_TAG_JOB, "media-col", media_col); + } - if (!job_cancelled) - { - /* - * Flag that the job should be cancelled... - */ + if ((keyword = cupsGetOption("output-bin", num_options, + options)) == NULL) + { + if ((choice = ppdFindMarkedChoice(ppd, "OutputBin")) != NULL) + keyword = _ppdCacheGetBin(pc, choice->choice); + } - job_cancelled = 1; - return; - } + if (keyword) + ippAddString(request, IPP_TAG_JOB, IPP_TAG_KEYWORD, "output-bin", + NULL, keyword); - /* - * The scheduler already tried to cancel us once, now just terminate - * after removing our temp files! - */ + color_attr_name = print_color_mode ? "print-color-mode" : "output-mode"; - if (tmpfilename[0]) - unlink(tmpfilename); + 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"); + } -#ifdef __APPLE__ - if (pstmpname[0]) - unlink(pstmpname); -#endif /* __APPLE__ */ + 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); + } - exit(1); -} + 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); + + /* + * Map FaxOut options... + */ + + if ((keyword = cupsGetOption("phone", num_options, options)) != NULL) + { + ipp_t *destination; /* destination collection */ + char tel_uri[1024]; /* tel: URI */ + + 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); + + if ((keyword = cupsGetOption("faxPrefix", num_options, + options)) != NULL && *keyword) + ippAddString(destination, IPP_TAG_JOB, IPP_TAG_TEXT, + "pre-dial-string", NULL, keyword); + + ippAddCollection(request, IPP_TAG_JOB, "destination-uris", destination); + ippDelete(destination); + } + } + else + { + /* + * 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 (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) + { + group = IPP_TAG_ZERO; + continue; + } + + if (group != ippGetGroupTag(attr)) + { + group = ippGetGroupTag(attr); + fprintf(stderr, "DEBUG: ---- %s ----\n", ippTagString(group)); + } + + ippAttributeString(attr, buffer, sizeof(buffer)); + fprintf(stderr, "DEBUG: %s %s%s %s\n", name, + ippGetCount(attr) > 1 ? "1setOf " : "", + ippTagString(ippGetValueTag(attr)), buffer); + } + + fprintf(stderr, "DEBUG: ---- %s ----\n", ippTagString(IPP_TAG_END)); + + return (request); +} + + +/* + * 'password_cb()' - Disable the password prompt for cupsDoFileRequest(). + */ + +static const char * /* O - Password */ +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) */ + int *password_tries) /* I - Password tries */ +{ + char def_username[HTTP_MAX_VALUE]; /* Default username */ + + + fprintf(stderr, "DEBUG: password_cb(prompt=\"%s\", http=%p, method=\"%s\", " + "resource=\"%s\", password_tries=%p(%d)), password=%p\n", + prompt, http, method, resource, password_tries, *password_tries, + password); + + (void)prompt; + (void)method; + (void)resource; + + /* + * 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) + { + (*password_tries) ++; + + return (password); + } + else + { + /* + * Give up after 3 tries or if we don't have a password to begin with... + */ + + 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; + + if (qend < q) + { + *q = '\0'; + return (q); + } + + *qptr++ = '\''; + *qptr++ = '\"'; + + while (*s && qptr < qend) + { + if (*s == '\\' || *s == '\"' || *s == '\'') + { + if (q < (qend - 3)) + { + *qptr++ = '\\'; + *qptr++ = '\\'; + *qptr++ = '\\'; + } + else + break; + } + + *qptr++ = *s++; + } + + *qptr++ = '\"'; + *qptr++ = '\''; + *qptr = '\0'; + + return (q); +} + + +/* + * 'report_attr()' - Report an IPP attribute value. + */ + +static void +report_attr(ipp_attribute_t *attr) /* I - Attribute */ +{ + int i; /* Looping var */ + char value[1024], /* Value string */ + *valptr; /* Pointer into value string */ + const char *cached; /* Cached attribute */ + + + /* + * Convert the attribute values into quoted strings... + */ + + for (i = 0, valptr = value; + i < attr->num_values && valptr < (value + sizeof(value) - 10); + i ++) + { + if (i > 0) + *valptr++ = ','; + + switch (attr->value_tag) + { + case IPP_TAG_INTEGER : + case IPP_TAG_ENUM : + snprintf(valptr, sizeof(value) - (valptr - value), "%d", + attr->values[i].integer); + valptr += strlen(valptr); + break; + + case IPP_TAG_TEXT : + case IPP_TAG_NAME : + case IPP_TAG_KEYWORD : + quote_string(attr->values[i].string.text, valptr, + value + sizeof(value) - valptr); + valptr += strlen(valptr); + break; + + default : + /* + * Unsupported value type... + */ + + return; + } + } + + *valptr = '\0'; + + _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); + } + + _cupsMutexUnlock(&report_mutex); +} + + +/* + * 'report_printer_state()' - Report the printer state. + */ + +static void +report_printer_state(ipp_t *ipp) /* I - IPP response */ +{ + ipp_attribute_t *pa, /* printer-alert */ + *pam, /* printer-alert-message */ + *psm, /* printer-state-message */ + *reasons, /* printer-state-reasons */ + *marker; /* marker-* attributes */ + char value[1024], /* State/message string */ + *valptr; /* Pointer into string */ + static int ipp_supplies = -1; + /* Report supply levels? */ + + + /* + * Report alerts and messages... + */ + + if ((pa = ippFindAttribute(ipp, "printer-alert", IPP_TAG_TEXT)) != NULL) + report_attr(pa); + + if ((pam = ippFindAttribute(ipp, "printer-alert-message", + IPP_TAG_TEXT)) != NULL) + report_attr(pam); + + if ((psm = ippFindAttribute(ipp, "printer-state-message", + IPP_TAG_TEXT)) != NULL) + { + char *ptr; /* Pointer into message */ + + + strlcpy(value, "INFO: ", sizeof(value)); + for (ptr = psm->values[0].string.text, valptr = value + 6; + *ptr && valptr < (value + sizeof(value) - 6); + ptr ++) + { + if (*ptr < ' ' && *ptr > 0 && *ptr != '\t') + { + /* + * Substitute "" for the control character; sprintf is safe because + * we always leave 6 chars free at the end... + */ + + sprintf(valptr, "<%02X>", *ptr); + valptr += 4; + } + else + *valptr++ = *ptr; + } + + *valptr++ = '\n'; + *valptr = '\0'; + + fputs(value, stderr); + } + + /* + * Now report printer-state-reasons, filtering out some of the reasons we never + * want to set... + */ + + if ((reasons = ippFindAttribute(ipp, "printer-state-reasons", + IPP_TAG_KEYWORD)) == NULL) + return; + + update_reasons(reasons, NULL); + + /* + * Relay the current marker-* attribute values... + */ + + 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); + } +} + + +#if defined(HAVE_GSSAPI) && defined(HAVE_XPC) +/* + * 'run_as_user()' - Run the IPP backend as the printing user. + * + * 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 */ +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 */ +{ + 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); + + /* + * Connect to the user agent for the specified UID... + */ + + conn = xpc_connection_create_mach_service(kPMPrintUIToolAgent, + dispatch_get_global_queue(0, 0), 0); + if (!conn) + { + _cupsLangPrintFilter(stderr, "ERROR", + _("Unable to start backend process.")); + fputs("DEBUG: Unable to create connection to agent.\n", stderr); + goto cleanup; + } + + 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) + { + 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 + { + _cupsLangPrintFilter(stderr, "ERROR", + _("Unable to start backend process.")); + fputs("DEBUG: No reply from agent.\n", stderr); + goto cleanup; + } + + /* + * Then wait for the backend to finish... + */ + + request = xpc_dictionary_create(NULL, NULL, 0); + xpc_dictionary_set_int64(request, "command", kPMWaitForJob); + xpc_dictionary_set_fd(request, "stderr", 2); + + 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) + { + 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); + } + + xpc_release(response); + } + else + _cupsLangPrintFilter(stderr, "ERROR", + _("Unable to get backend exit status.")); + + cleanup: + + if (conn) + { + xpc_connection_cancel(conn); + xpc_release(conn); + } + + return (status); +} +#endif /* HAVE_GSSAPI && HAVE_XPC */ + + +/* + * 'sigterm_handler()' - Handle 'terminate' signals that stop the backend. + */ + +static void +sigterm_handler(int sig) /* I - Signal */ +{ + (void)sig; /* remove compiler warnings... */ + + write(2, "DEBUG: Got SIGTERM.\n", 20); + +#if defined(HAVE_GSSAPI) && defined(HAVE_XPC) + if (child_pid) + { + kill(child_pid, sig); + child_pid = 0; + } +#endif /* HAVE_GSSAPI && HAVE_XPC */ + + if (!job_canceled) + { + /* + * Flag that the job should be canceled... + */ + + write(2, "DEBUG: job_canceled = 1.\n", 25); + + job_canceled = 1; + return; + } + + /* + * The scheduler already tried to cancel us once, now just terminate + * after removing our temp file! + */ + + 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 (attr) + { + int i; /* Looping var */ + + new_reasons = cupsArrayNew((cups_array_func_t)strcmp, NULL); + op = '\0'; + + 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 (*s == '+' || *s == '-') + op = *s++; + else + op = '\0'; + + new_reasons = _cupsArrayNewStrings(s, ','); + } + else + return; + + /* + * Compute the changes... + */ + + 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)); + + _cupsMutexLock(&report_mutex); + + 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 = ","; + + cupsArrayRemove(state_reasons, reason); + } + } + } + else + { + /* + * Replace reasons... + */ + + 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); + + /* + * Report changes and return... + */ + + 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 6687 2007-07-18 19:49:45Z mike $". + * End of "$Id$". */