/* * "$Id: ipp.c,v 1.1.1.6 2002/06/06 22:12:29 jlovell Exp $" * * IPP backend for the Common UNIX Printing System (CUPS). * * Copyright 1997-2002 by Easy Software Products, all rights reserved. * * These coded instructions, statements, and computer programs are the * property of Easy Software Products and are protected by Federal * copyright law. Distribution and use rights are outlined in the file * "LICENSE" which should have been included with this file. If this * file is missing or damaged please contact Easy Software Products * at: * * Attn: CUPS Licensing Information * Easy Software Products * 44141 Airport View Drive, Suite 204 * Hollywood, Maryland 20636-3111 USA * * Voice: (301) 373-9603 * EMail: cups-info@cups.org * WWW: http://www.cups.org * * This file is subject to the Apple OS-Developed Software exception. * * Contents: * * main() - Send a file to the printer or server. * password_cb() - Disable the password prompt for * cupsDoFileRequest(). * report_printer_state() - Report the printer state. */ /* * Include necessary headers. */ #include #include #include #include #include #include #include #include #include /* * Local functions... */ const char *password_cb(const char *); int report_printer_state(ipp_t *ipp); /* * Local globals... */ char *password = NULL; /* * 'main()' - Send a file to the printer or server. * * Usage: * * printer-uri job-id user title copies options [file] */ int /* O - Exit status */ main(int argc, /* I - Number of command-line arguments (6 or 7) */ char *argv[]) /* I - Command-line arguments */ { int i; /* Looping var */ int num_options; /* Number of printer options */ cups_option_t *options; /* Printer options */ char method[255], /* Method in URI */ hostname[1024], /* Hostname */ username[255], /* Username info */ resource[1024], /* Resource info (printer name) */ filename[1024]; /* File to print */ int port; /* Port number (not used) */ char uri[HTTP_MAX_URI];/* Updated URI without user/pass */ 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 */ ipp_attribute_t *job_id_attr; /* job-id attribute */ int job_id; /* job-id value */ ipp_attribute_t *job_state; /* job-state attribute */ ipp_attribute_t *copies_sup; /* copies-supported attribute */ ipp_attribute_t *charset_sup; /* charset-supported attribute */ ipp_attribute_t *format_sup; /* document-format-supported attribute */ const char *charset; /* Character set to use */ cups_lang_t *language; /* Default language */ int copies; /* Number of copies remaining */ const char *content_type; /* CONTENT_TYPE environment variable */ #if defined(HAVE_SIGACTION) && !defined(HAVE_SIGSET) struct sigaction action; /* Actions for POSIX signals */ #endif /* HAVE_SIGACTION && !HAVE_SIGSET */ int version; /* IPP version */ int reasons; /* Number of printer-state-reasons shown */ /* * Make sure status messages are not buffered... */ setbuf(stderr, NULL); /* * Check command-line... */ if (argc == 1) { char *s; if ((s = strrchr(argv[0], '/')) != NULL) s ++; else s = argv[0]; printf("network %s \"Unknown\" \"Internet Printing Protocol (%s)\"\n", s, s); return (0); } else if (argc < 6 || argc > 7) { fprintf(stderr, "Usage: %s job-id user title copies options [file]\n", argv[0]); return (1); } /* * If we have 7 arguments, print the file named on the command-line. * Otherwise, copy stdin to a temporary file and print the temporary * file. */ if (argc == 6) { /* * Copy stdin to a temporary file... */ int fd; /* Temporary file */ char buffer[8192]; /* Buffer for copying */ int bytes; /* Number of bytes read */ if ((fd = cupsTempFd(filename, sizeof(filename))) < 0) { perror("ERROR: unable to create temporary file"); return (1); } while ((bytes = fread(buffer, 1, sizeof(buffer), stdin)) > 0) if (write(fd, buffer, bytes) < bytes) { perror("ERROR: unable to write to temporary file"); close(fd); unlink(filename); return (1); } close(fd); } else strlcpy(filename, argv[6], sizeof(filename)); /* * Extract the hostname and printer name from the URI... */ httpSeparate(argv[0], method, username, hostname, &port, resource); /* * Set the authentication info, if any... */ cupsSetPasswordCB(password_cb); if (username[0]) { if ((password = strchr(username, ':')) != NULL) *password++ = '\0'; cupsSetUser(username); } /* * Try connecting to the remote server... */ do { fprintf(stderr, "INFO: Connecting to %s on port %d...\n", hostname, port); if ((http = httpConnect(hostname, port)) == NULL) { if (errno == ECONNREFUSED || errno == EHOSTDOWN || errno == EHOSTUNREACH) { fprintf(stderr, "INFO: Network host \'%s\' is busy; will retry in 30 seconds...", hostname); sleep(30); } else { perror("ERROR: Unable to connect to IPP host"); sleep(30); } } } while (http == NULL); fprintf(stderr, "INFO: Connected to %s...\n", hostname); /* * Build a URI for the printer and fill the standard IPP attributes for * an IPP_PRINT_FILE request. We can't use the URI in argv[0] because it * might contain username:password information... */ snprintf(uri, sizeof(uri), "%s://%s:%d%s", method, 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... */ language = cupsLangDefault(); charset_sup = NULL; copies_sup = NULL; format_sup = NULL; version = 1; supported = NULL; do { /* * Build the IPP request... */ request = ippNew(); request->request.op.version[1] = version; request->request.op.operation_id = IPP_GET_PRINTER_ATTRIBUTES; request->request.op.request_id = 1; ippAddString(request, IPP_TAG_OPERATION, IPP_TAG_CHARSET, "attributes-charset", NULL, "utf-8"); ippAddString(request, IPP_TAG_OPERATION, IPP_TAG_LANGUAGE, "attributes-natural-language", NULL, language != NULL ? language->language : "en"); ippAddString(request, IPP_TAG_OPERATION, IPP_TAG_URI, "printer-uri", NULL, uri); /* * Do the request... */ 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 (ipp_status > IPP_OK_CONFLICT) { if (ipp_status == IPP_PRINTER_BUSY || ipp_status == IPP_SERVICE_UNAVAILABLE) { fputs("INFO: Printer busy; will retry in 10 seconds...\n", stderr); report_printer_state(supported); sleep(10); } else if ((ipp_status == IPP_BAD_REQUEST || ipp_status == IPP_VERSION_NOT_SUPPORTED) && version == 1) { /* * Switch to IPP/1.0... */ fputs("INFO: Printer does not support IPP/1.1, trying IPP/1.0...\n", stderr); version = 0; httpReconnect(http); } else fprintf(stderr, "ERROR: Unable to get printer status (%s)!\n", ippErrorString(ipp_status)); if (supported) ippDelete(supported); continue; } else if ((copies_sup = ippFindAttribute(supported, "copies-supported", IPP_TAG_RANGE)) != NULL) { /* * Has the "copies-supported" attribute - does it have an upper * bound > 1? */ if (copies_sup->values[0].range.upper <= 1) copies_sup = NULL; /* No */ } charset_sup = ippFindAttribute(supported, "charset-supported", IPP_TAG_CHARSET); format_sup = ippFindAttribute(supported, "document-format-supported", IPP_TAG_MIMETYPE); if (format_sup) { fprintf(stderr, "DEBUG: document-format-supported (%d values)\n", format_sup->num_values); for (i = 0; i < format_sup->num_values; i ++) fprintf(stderr, "DEBUG: [%d] = \"%s\"\n", i, format_sup->values[i].string.text); } report_printer_state(supported); } while (ipp_status > IPP_OK_CONFLICT); /* * Now that we are "connected" to the port, ignore SIGTERM so that we * can finish out any page data the driver sends (e.g. to eject the * current page... Only ignore SIGTERM if we are printing data from * stdin (otherwise you can't cancel raw jobs...) */ if (argc < 7) { #ifdef HAVE_SIGSET /* Use System V signals over POSIX to avoid bugs */ sigset(SIGTERM, SIG_IGN); #elif defined(HAVE_SIGACTION) memset(&action, 0, sizeof(action)); sigemptyset(&action.sa_mask); action.sa_handler = SIG_IGN; sigaction(SIGTERM, &action, NULL); #else signal(SIGTERM, SIG_IGN); #endif /* HAVE_SIGSET */ } /* * See if the printer supports multiple copies... */ if (copies_sup || argc < 7) copies = 1; else copies = atoi(argv[4]); /* * Figure out the character set to use... */ charset = language ? cupsLangEncoding(language) : "us-ascii"; if (charset_sup) { /* * See if IPP server supports the requested character set... */ for (i = 0; i < charset_sup->num_values; i ++) if (strcasecmp(charset, charset_sup->values[i].string.text) == 0) break; /* * If not, choose us-ascii or utf-8... */ if (i >= charset_sup->num_values) { /* * See if us-ascii is supported... */ for (i = 0; i < charset_sup->num_values; i ++) if (strcasecmp("us-ascii", charset_sup->values[i].string.text) == 0) break; if (i < charset_sup->num_values) charset = "us-ascii"; else charset = "utf-8"; } } /* * Then issue the print-job request... */ reasons = 0; while (copies > 0) { /* * Build the IPP request... */ request = ippNew(); request->request.op.version[1] = version; request->request.op.operation_id = IPP_PRINT_JOB; request->request.op.request_id = 1; ippAddString(request, IPP_TAG_OPERATION, IPP_TAG_CHARSET, "attributes-charset", NULL, charset); ippAddString(request, IPP_TAG_OPERATION, IPP_TAG_LANGUAGE, "attributes-natural-language", NULL, language != NULL ? language->language : "en"); ippAddString(request, IPP_TAG_OPERATION, IPP_TAG_URI, "printer-uri", NULL, uri); fprintf(stderr, "DEBUG: printer-uri = \"%s\"\n", uri); if (argv[2][0]) ippAddString(request, IPP_TAG_OPERATION, IPP_TAG_NAME, "requesting-user-name", NULL, argv[2]); fprintf(stderr, "DEBUG: requesting-user-name = \"%s\"\n", argv[2]); if (argv[3][0]) ippAddString(request, IPP_TAG_OPERATION, IPP_TAG_NAME, "job-name", NULL, argv[3]); fprintf(stderr, "DEBUG: job-name = \"%s\"\n", argv[3]); /* * Handle options on the command-line... */ options = NULL; num_options = cupsParseOptions(argv[5], 0, &options); if (argc > 6) content_type = getenv("CONTENT_TYPE"); else content_type = "application/vnd.cups-raw"; 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) == 0) break; if (i < format_sup->num_values) num_options = cupsAddOption("document-format", content_type, num_options, &options); } if (copies_sup) { /* * Only send options if the destination printer supports the copies * attribute. This is a hack for the HP JetDirect implementation of * IPP, which does not accept extension attributes and incorrectly * reports a client-error-bad-request error instead of the * successful-ok-unsupported-attributes status. In short, at least * some HP implementations of IPP are non-compliant. */ cupsEncodeOptions(request, num_options, options); ippAddInteger(request, IPP_TAG_JOB, IPP_TAG_INTEGER, "copies", atoi(argv[4])); } cupsFreeOptions(num_options, options); /* * 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... */ if (!copies_sup) httpReconnect(http); /* * Do the request... */ if ((response = cupsDoFileRequest(http, request, resource, filename)) == NULL) ipp_status = cupsLastError(); else ipp_status = response->request.status.status_code; if (ipp_status > IPP_OK_CONFLICT) { job_id = 0; if (ipp_status == IPP_SERVICE_UNAVAILABLE || ipp_status == IPP_PRINTER_BUSY) { fputs("INFO: Printer is busy; retrying print job...\n", stderr); sleep(10); } else fprintf(stderr, "ERROR: Print file was not accepted (%s)!\n", ippErrorString(ipp_status)); } else if ((job_id_attr = ippFindAttribute(response, "job-id", IPP_TAG_INTEGER)) == NULL) { fputs("INFO: Print file accepted - job ID unknown.\n", stderr); job_id = 0; } else { job_id = job_id_attr->values[0].integer; fprintf(stderr, "INFO: Print file accepted - job ID %d.\n", job_id); } if (response) ippDelete(response); if (ipp_status <= IPP_OK_CONFLICT && argc > 6) { fprintf(stderr, "PAGE: 1 %d\n", copies_sup ? atoi(argv[4]) : 1); copies --; } else if (ipp_status != IPP_SERVICE_UNAVAILABLE && ipp_status != IPP_PRINTER_BUSY) break; /* * Wait for the job to complete... */ if (!job_id) continue; fputs("INFO: Waiting for job to complete...\n", stderr); for (;;) { /* * Build an IPP_GET_JOB_ATTRIBUTES request... */ request = ippNew(); request->request.op.version[1] = version; request->request.op.operation_id = IPP_GET_JOB_ATTRIBUTES; request->request.op.request_id = 1; ippAddString(request, IPP_TAG_OPERATION, IPP_TAG_CHARSET, "attributes-charset", NULL, charset); ippAddString(request, IPP_TAG_OPERATION, IPP_TAG_LANGUAGE, "attributes-natural-language", NULL, language != NULL ? language->language : "en"); ippAddString(request, IPP_TAG_OPERATION, IPP_TAG_URI, "printer-uri", NULL, uri); ippAddInteger(request, IPP_TAG_OPERATION, IPP_TAG_INTEGER, "job-id", job_id); if (argv[2][0]) ippAddString(request, IPP_TAG_OPERATION, IPP_TAG_NAME, "requesting-user-name", NULL, argv[2]); ippAddString(request, IPP_TAG_OPERATION, IPP_TAG_KEYWORD, "requested-attributes", NULL, "job-state"); /* * Do the request... */ if (!copies_sup) httpReconnect(http); if ((response = cupsDoRequest(http, request, resource)) == NULL) ipp_status = cupsLastError(); else ipp_status = response->request.status.status_code; if (ipp_status == IPP_NOT_FOUND) { /* * Job has gone away and/or the server has no job history... */ ippDelete(response); ipp_status = IPP_OK; break; } if (ipp_status > IPP_OK_CONFLICT) { if (ipp_status != IPP_SERVICE_UNAVAILABLE && ipp_status != IPP_PRINTER_BUSY) { if (response) ippDelete(response); fprintf(stderr, "ERROR: Unable to get job %d attributes (%s)!\n", job_id, ippErrorString(ipp_status)); break; } } else if ((job_state = ippFindAttribute(response, "job-state", IPP_TAG_ENUM)) != NULL) { /* * Stop polling if the job is finished or pending-held... */ if (job_state->values[0].integer > IPP_JOB_PROCESSING || job_state->values[0].integer == IPP_JOB_HELD) { ippDelete(response); break; } } if (response) ippDelete(response); /* * Now check on the printer state... */ request = ippNew(); request->request.op.version[1] = version; request->request.op.operation_id = IPP_GET_PRINTER_ATTRIBUTES; request->request.op.request_id = 1; ippAddString(request, IPP_TAG_OPERATION, IPP_TAG_CHARSET, "attributes-charset", NULL, charset); ippAddString(request, IPP_TAG_OPERATION, IPP_TAG_LANGUAGE, "attributes-natural-language", NULL, language != NULL ? language->language : "en"); ippAddString(request, IPP_TAG_OPERATION, IPP_TAG_URI, "printer-uri", NULL, uri); if (argv[2][0]) ippAddString(request, IPP_TAG_OPERATION, IPP_TAG_NAME, "requesting-user-name", NULL, argv[2]); ippAddString(request, IPP_TAG_OPERATION, IPP_TAG_KEYWORD, "requested-attributes", NULL, "printer-state-reasons"); /* * Do the request... */ if (!copies_sup) httpReconnect(http); if ((response = cupsDoRequest(http, request, resource)) != NULL) { reasons = report_printer_state(response); ippDelete(response); } /* * Wait 10 seconds before polling again... */ sleep(10); } } /* * Free memory... */ httpClose(http); if (supported) ippDelete(supported); /* * Close and remove the temporary file if necessary... */ if (argc < 7) unlink(filename); /* * Return the queue status... */ if (ipp_status <= IPP_OK_CONFLICT && reasons == 0) fputs("INFO: Ready to print.\n", stderr); return (ipp_status > IPP_OK_CONFLICT); } /* * 'password_cb()' - Disable the password prompt for cupsDoFileRequest(). */ const char * /* O - Password */ password_cb(const char *prompt) /* I - Prompt (not used) */ { (void)prompt; return (password); } /* * 'report_printer_state()' - Report the printer state. */ 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 *reasons; /* printer-state-reasons */ const char *message; /* Message to show */ char unknown[1024]; /* Unknown message string */ if ((reasons = ippFindAttribute(ipp, "printer-state-reasons", IPP_TAG_KEYWORD)) == NULL) return (0); for (i = 0, count = 0; i < reasons->num_values; i ++) { message = NULL; if (strncmp(reasons->values[i].string.text, "media-needed", 12) == 0) message = "Media tray needs to be filled."; else if (strncmp(reasons->values[i].string.text, "media-jam", 9) == 0) message = "Media jam!"; else if (strncmp(reasons->values[i].string.text, "moving-to-paused", 16) == 0 || strncmp(reasons->values[i].string.text, "paused", 6) == 0 || strncmp(reasons->values[i].string.text, "shutdown", 8) == 0) message = "Printer off-line."; else if (strncmp(reasons->values[i].string.text, "toner-low", 9) == 0) message = "Toner low."; else if (strncmp(reasons->values[i].string.text, "toner-empty", 11) == 0) message = "Out of toner!"; else if (strncmp(reasons->values[i].string.text, "cover-open", 10) == 0) message = "Cover open."; else if (strncmp(reasons->values[i].string.text, "interlock-open", 14) == 0) message = "Interlock open."; else if (strncmp(reasons->values[i].string.text, "door-open", 9) == 0) message = "Door open."; else if (strncmp(reasons->values[i].string.text, "input-tray-missing", 18) == 0) message = "Media tray missing!"; else if (strncmp(reasons->values[i].string.text, "media-low", 9) == 0) message = "Media tray almost empty."; else if (strncmp(reasons->values[i].string.text, "media-empty", 11) == 0) message = "Media tray empty!"; else if (strncmp(reasons->values[i].string.text, "output-tray-missing", 19) == 0) message = "Output tray missing!"; else if (strncmp(reasons->values[i].string.text, "output-area-almost-full", 23) == 0) message = "Output bin almost full."; else if (strncmp(reasons->values[i].string.text, "output-area-full", 16) == 0) message = "Output bin full!"; else if (strncmp(reasons->values[i].string.text, "marker-supply-low", 17) == 0) message = "Ink/toner almost empty."; else if (strncmp(reasons->values[i].string.text, "marker-supply-empty", 19) == 0) message = "Ink/toner empty!"; else if (strncmp(reasons->values[i].string.text, "marker-waste-almost-full", 24) == 0) message = "Ink/toner waste bin almost full."; else if (strncmp(reasons->values[i].string.text, "marker-waste-full", 17) == 0) message = "Ink/toner waste bin full!"; else if (strncmp(reasons->values[i].string.text, "fuser-over-temp", 15) == 0) message = "Fuser temperature high!"; else if (strncmp(reasons->values[i].string.text, "fuser-under-temp", 16) == 0) message = "Fuser temperature low!"; else if (strncmp(reasons->values[i].string.text, "opc-near-eol", 12) == 0) message = "OPC almost at end-of-life."; else if (strncmp(reasons->values[i].string.text, "opc-life-over", 13) == 0) message = "OPC at end-of-life!"; else if (strncmp(reasons->values[i].string.text, "developer-low", 13) == 0) message = "Developer almost empty."; else if (strncmp(reasons->values[i].string.text, "developer-empty", 15) == 0) message = "Developer empty!"; else if (strstr(reasons->values[i].string.text, "error") != NULL) { message = unknown; snprintf(unknown, sizeof(unknown), "Unknown printer error (%s)!", reasons->values[i].string.text); } if (message) { 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); } } return (count); } /* * End of "$Id: ipp.c,v 1.1.1.6 2002/06/06 22:12:29 jlovell Exp $". */