// // IPP Everywhere printer application for CUPS. // // Copyright © 2020-2026 by OpenPrinting. // Copyright © 2020 by the IEEE-ISTO Printer Working Group. // Copyright © 2010-2021 by Apple Inc. // // Licensed under Apache License v2.0. See the file "LICENSE" for more // information. // // Note: This program began life as the "ippserver" sample code that first // appeared in CUPS 1.4. The name has been changed in order to distinguish it // from the PWG's much more ambitious "ippserver" program, which supports // different kinds of IPP services and multiple services per instance - the // "ippeveprinter" program exposes a single print service conforming to the // current IPP Everywhere specification, thus the new name. // #include #include #if !CUPS_LITE # include #endif /* !CUPS_LITE */ #include #include #ifdef _WIN32 # include # include # include # define WEXITSTATUS(s) (s) # include typedef ULONG nfds_t; # define poll WSAPoll #else extern char **environ; # include # include # include # include #endif // _WIN32 #ifndef O_BINARY # define O_BINARY 0 // Windows "binary file" nonsense #endif // !O_BINARY #ifdef HAVE_SYS_MOUNT_H # include #endif // HAVE_SYS_MOUNT_H #ifdef HAVE_SYS_STATFS_H # include #endif // HAVE_SYS_STATFS_H #ifdef HAVE_SYS_STATVFS_H # include #endif // HAVE_SYS_STATVFS_H #ifdef HAVE_SYS_VFS_H # include #endif // HAVE_SYS_VFS_H #if HAVE_LIBPAM # ifdef HAVE_PAM_PAM_APPL_H # include # else # include # endif // HAVE_PAM_PAM_APPL_H #endif // HAVE_LIBPAM #include "printer-png.h" #include "printer-lg-png.h" #include "printer-sm-png.h" // // Constants... // enum ippeve_preason_e // printer-state-reasons bit values { IPPEVE_PREASON_NONE = 0x0000, // none IPPEVE_PREASON_OTHER = 0x0001, // other IPPEVE_PREASON_COVER_OPEN = 0x0002, // cover-open IPPEVE_PREASON_INPUT_TRAY_MISSING = 0x0004, // input-tray-missing IPPEVE_PREASON_MARKER_SUPPLY_EMPTY = 0x0008, // marker-supply-empty IPPEVE_PREASON_MARKER_SUPPLY_LOW = 0x0010, // marker-supply-low IPPEVE_PREASON_MARKER_WASTE_ALMOST_FULL = 0x0020, // marker-waste-almost-full IPPEVE_PREASON_MARKER_WASTE_FULL = 0x0040, // marker-waste-full IPPEVE_PREASON_MEDIA_EMPTY = 0x0080, // media-empty IPPEVE_PREASON_MEDIA_JAM = 0x0100, // media-jam IPPEVE_PREASON_MEDIA_LOW = 0x0200, // media-low IPPEVE_PREASON_MEDIA_NEEDED = 0x0400, // media-needed IPPEVE_PREASON_MOVING_TO_PAUSED = 0x0800, // moving-to-paused IPPEVE_PREASON_PAUSED = 0x1000, // paused IPPEVE_PREASON_SPOOL_AREA_FULL = 0x2000,// spool-area-full IPPEVE_PREASON_TONER_EMPTY = 0x4000, // toner-empty IPPEVE_PREASON_TONER_LOW = 0x8000 // toner-low }; typedef unsigned int ippeve_preason_t; // Bitfield for printer-state-reasons static const char * const ippeve_preason_strings[] = { // Strings for each bit // "none" is implied for no bits set "other", "cover-open", "input-tray-missing", "marker-supply-empty", "marker-supply-low", "marker-waste-almost-full", "marker-waste-full", "media-empty", "media-jam", "media-low", "media-needed", "moving-to-paused", "paused", "spool-area-full", "toner-empty", "toner-low" }; // // URL scheme for web resources... // #define WEB_SCHEME "https" // // Structures... // #if HAVE_LIBPAM typedef struct ippeve_authdata_s // Authentication data { char username[HTTP_MAX_VALUE], // Username string *password; // Password string } ippeve_authdata_t; #endif // HAVE_LIBPAM typedef struct ippeve_filter_s // Attribute filter { cups_array_t *ra; // Requested attributes ipp_tag_t group_tag; // Group to copy } ippeve_filter_t; typedef struct ippeve_job_s ippeve_job_t; typedef struct ippeve_printer_s // Printer data { // Note: A "real" IPP implementation will support more than one IPv4 and one IPv6 listener int ipv4, // IPv4 listener ipv6; // IPv6 listener cups_dnssd_t *dnssd; // DNS-SD context cups_dnssd_service_t *services; // DNS-SD services char *dnssd_subtypes;// DNS-SD subtypes int dnssd_collision;// Name collision? char *dnssd_name, // printer-dns-sd-name *name, // printer-name *icons[3], // Icon filenames *strings, // Strings filename *directory, // Spool directory *hostname, // Hostname *device_uri, // Device URI (if any) *output_format, // Output format *command; // Command to run with job file int port; // Port bool web_forms; // Enable web interface forms? size_t urilen; // Length of printer URI ipp_t *attrs; // Static attributes time_t start_time; // Startup time time_t config_time; // printer-config-change-time ipp_pstate_t state; // printer-state value ippeve_preason_t state_reasons; // printer-state-reasons values time_t state_time; // printer-state-change-time cups_array_t *jobs; // Jobs ippeve_job_t *active_job; // Current active/pending job int next_job_id; // Next job-id value cups_rwlock_t rwlock; // Printer lock } ippeve_printer_t; struct ippeve_job_s // Job data { int id; // Job ID const char *name, // job-name *username, // job-originating-user-name *format; // document-format ipp_jstate_t state; // job-state value char *message; // job-state-message value int msglevel; // job-state-message log level (0=error, 1=info) time_t created, // time-at-creation value processing, // time-at-processing value completed; // time-at-completed value int impressions, // job-impressions value impcompleted; // job-impressions-completed value ipp_t *attrs; // Static attributes char password[256]; // Document password, if any int cancel; // Non-zero when job canceled char *filename; // Print file name int fd; // Print file descriptor ippeve_printer_t *printer; // Printer }; typedef struct ippeve_client_s // Client data { http_t *http; // HTTP connection ipp_t *request, // IPP request *response; // IPP response time_t start; // Request start time http_state_t operation; // Request operation ipp_op_t operation_id; // IPP operation-id char uri[1024], // Request URI *options, // URI options host_field[HTTP_MAX_VALUE]; // Host: header int host_port; // Port number from Host: header http_addr_t addr; // Client address char hostname[256], // Client hostname username[HTTP_MAX_VALUE]; // Authenticated username, if any ippeve_printer_t *printer; // Printer ippeve_job_t *job; // Current job, if any } ippeve_client_t; // // Local functions... // static http_status_t authenticate_request(ippeve_client_t *client); static void clean_jobs(ippeve_printer_t *printer); static int compare_jobs(ippeve_job_t *a, ippeve_job_t *b, void *data); static void copy_attributes(ipp_t *to, ipp_t *from, cups_array_t *ra, ipp_tag_t group_tag, bool quickcopy); static void copy_job_attributes(ippeve_client_t *client, ippeve_job_t *job, cups_array_t *ra); static ippeve_client_t *create_client(ippeve_printer_t *printer, int sock); static ippeve_job_t *create_job(ippeve_client_t *client); static int create_job_file(ippeve_job_t *job, char *fname, size_t fnamesize, const char *dir, const char *ext); static int create_listener(const char *name, int port, int family); static ipp_t *create_media_col(const char *media, const char *source, const char *type, ipp_t *media_size, int bottom, int left, int right, int top); static ipp_t *create_media_size(int width, int length); static ipp_t *create_media_size_range(int min_width, int max_width, int min_length, int max_length); static ippeve_printer_t *create_printer(const char *servername, int serverport, const char *name, const char *location, const char *icons, const char *strings, cups_array_t *docformats, const char *subtypes, const char *directory, const char *command, const char *device_uri, const char *output_format, ipp_t *attrs); static void debug_attributes(const char *title, ipp_t *ipp, int response); static void delete_client(ippeve_client_t *client); static void delete_job(ippeve_job_t *job); static void delete_printer(ippeve_printer_t *printer); static void dnssd_callback(cups_dnssd_service_t *service, ippeve_printer_t *printer, cups_dnssd_flags_t flags); static int filter_cb(ippeve_filter_t *filter, ipp_t *dst, ipp_attribute_t *attr); static ippeve_job_t *find_job(ippeve_client_t *client); static void finish_document_data(ippeve_client_t *client, ippeve_job_t *job); static void finish_document_uri(ippeve_client_t *client, ippeve_job_t *job); static void flush_document_data(ippeve_client_t *client); static bool have_document_data(ippeve_client_t *client); static bool html_escape(ippeve_client_t *client, const char *s, size_t slen); static bool html_footer(ippeve_client_t *client); static bool html_header(ippeve_client_t *client, const char *title, int refresh); static bool html_printf(ippeve_client_t *client, const char *format, ...) _CUPS_FORMAT(2, 3); static void ipp_cancel_job(ippeve_client_t *client); static void ipp_cancel_jobs(ippeve_client_t *client); static void ipp_close_job(ippeve_client_t *client); static void ipp_create_job(ippeve_client_t *client); static void ipp_get_job_attributes(ippeve_client_t *client); static void ipp_get_jobs(ippeve_client_t *client); static void ipp_get_printer_attributes(ippeve_client_t *client); static void ipp_identify_printer(ippeve_client_t *client); static void ipp_print_job(ippeve_client_t *client); static void ipp_print_uri(ippeve_client_t *client); static void ipp_send_document(ippeve_client_t *client); static void ipp_send_uri(ippeve_client_t *client); static void ipp_validate_job(ippeve_client_t *client); static ipp_t *load_ippfile_attributes(const char *servername, int serverport, const char *filename, cups_array_t *docformats); static ipp_t *load_legacy_attributes(const char *make, const char *model, int ppm, int ppm_color, int duplex, cups_array_t *docformats); #if !CUPS_LITE static ipp_t *load_ppd_attributes(const char *ppdfile, cups_array_t *docformats); #endif /* !CUPS_LITE */ #if HAVE_LIBPAM static int pam_func(int, const struct pam_message **, struct pam_response **, void *); #endif // HAVE_LIBPAM static int parse_options(ippeve_client_t *client, cups_option_t **options); static void process_attr_message(ippeve_job_t *job, char *message); static void *process_client(ippeve_client_t *client); static int process_http(ippeve_client_t *client); static int process_ipp(ippeve_client_t *client); static void *process_job(ippeve_job_t *job); static void process_state_message(ippeve_job_t *job, char *message); static bool register_printer(ippeve_printer_t *printer); static bool respond_http(ippeve_client_t *client, http_status_t code, const char *content_coding, const char *type, size_t length); static void respond_ignored(ippeve_client_t *client, ipp_attribute_t *attr); static void respond_ipp(ippeve_client_t *client, ipp_status_t status, const char *message, ...) _CUPS_FORMAT(3, 4); static void respond_unsupported(ippeve_client_t *client, ipp_attribute_t *attr); static void run_printer(ippeve_printer_t *printer); static int show_media(ippeve_client_t *client); static int show_status(ippeve_client_t *client); static int show_supplies(ippeve_client_t *client); #ifndef _WIN32 static void signal_handler(int signum); #endif // !_WIN32 static char *time_string(time_t tv, char *buffer, size_t bufsize); static void usage(int status) _CUPS_NORETURN; static bool valid_doc_attributes(ippeve_client_t *client); static bool valid_job_attributes(ippeve_client_t *client); // // Globals... // static int KeepFiles = 0, // Keep spooled job files? MaxVersion = 20,// Maximum IPP version (20 = 2.0, 11 = 1.1, etc.) Verbosity = 0; // Verbosity level static const char *PAMService = NULL; // PAM service #ifndef _WIN32 static int StopPrinter = 0;// Stop the printer server? #endif // !_WIN32 // // 'main()' - Main entry to the sample server. // int // O - Exit status main(int argc, // I - Number of command-line args char *argv[]) // I - Command-line arguments { int i; // Looping var const char *opt, // Current option character *attrfile = NULL, // ippserver attributes file *command = NULL, // Command to run with job files *device_uri = NULL, // Device URI *output_format = NULL, // Output format *icon = NULL, // Icon file *keypath = NULL, // Keychain path *location = "", // Location of printer *make = "Example", // Manufacturer *model = "Printer", // Model *name = NULL, // Printer name #if !CUPS_LITE *ppdfile = NULL, /* PPD file */ #endif /* !CUPS_LITE */ *strings = NULL, // Strings file *subtypes = "_print"; // DNS-SD service subtype bool legacy = false, // Legacy mode? duplex = false, // Duplex mode web_forms = true; // Enable web site forms? int ppm = 10, // Pages per minute for mono ppm_color = 0; // Pages per minute for color ipp_t *attrs = NULL; // Printer attributes char directory[1024] = ""; // Spool directory cups_array_t *docformats = NULL; // Supported formats const char *servername = NULL; // Server host name int serverport = 0; // Server port number (0 = auto) ippeve_printer_t *printer; // Printer object _cupsSetLocale(argv); // Parse command-line arguments... for (i = 1; i < argc; i ++) { if (!strcmp(argv[i], "--help")) { usage(0); } else if (!strcmp(argv[i], "--no-web-forms")) { web_forms = false; } else if (!strcmp(argv[i], "--pam-service")) { i ++; if (i >= argc) usage(1); PAMService = argv[i]; } else if (!strcmp(argv[i], "--version")) { puts(CUPS_SVERSION); return (0); } else if (!strncmp(argv[i], "--", 2)) { _cupsLangPrintf(stderr, _("%s: Unknown option \"%s\"."), argv[0], argv[i]); usage(1); } else if (argv[i][0] == '-') { for (opt = argv[i] + 1; *opt; opt ++) { switch (*opt) { case '2' : // -2 (enable 2-sided printing) duplex = true; legacy = true; break; case 'A' : // -A (enable authentication) if (!PAMService) PAMService = "cups"; break; case 'D' : // -D device-uri i ++; if (i >= argc) usage(1); device_uri = argv[i]; break; case 'F' : // -F output/format i ++; if (i >= argc) usage(1); output_format = argv[i]; break; case 'K' : // -K keypath i ++; if (i >= argc) usage(1); keypath = argv[i]; break; case 'M' : // -M manufacturer i ++; if (i >= argc) usage(1); make = argv[i]; legacy = true; break; #if !CUPS_LITE case 'P' : /* -P filename.ppd */ i ++; if (i >= argc) usage(1); ppdfile = argv[i]; break; #endif /* !CUPS_LITE */ case 'S' : // -S filename.strings i ++; if (i >= argc) usage(1); strings = argv[i]; break; case 'V' : // -V max-version i ++; if (i >= argc) usage(1); if (!strcmp(argv[i], "2.0")) MaxVersion = 20; else if (!strcmp(argv[i], "1.1")) MaxVersion = 11; else usage(1); break; case 'a' : // -a attributes-file i ++; if (i >= argc) usage(1); attrfile = argv[i]; break; case 'c' : // -c command i ++; if (i >= argc) usage(1); command = argv[i]; break; case 'd' : // -d spool-directory i ++; if (i >= argc) usage(1); cupsCopyString(directory, argv[i], sizeof(directory)); break; case 'f' : // -f type/subtype[,...] i ++; if (i >= argc) usage(1); docformats = cupsArrayNewStrings(argv[i], ','); break; case 'i' : // -i icon.png i ++; if (i >= argc) usage(1); icon = argv[i]; break; case 'k' : // -k (keep files) KeepFiles = 1; break; case 'l' : // -l location i ++; if (i >= argc) usage(1); location = argv[i]; break; case 'm' : // -m model i ++; if (i >= argc) usage(1); model = argv[i]; legacy = true; break; case 'n' : // -n hostname i ++; if (i >= argc) usage(1); servername = argv[i]; break; case 'p' : // -p port i ++; if (i >= argc || !_cups_isdigit(argv[i][0])) usage(1); serverport = atoi(argv[i]); break; case 'r' : // -r subtype i ++; if (i >= argc) usage(1); subtypes = argv[i]; break; case 's' : // -s speed[,color-speed] i ++; if (i >= argc) usage(1); if (sscanf(argv[i], "%d,%d", &ppm, &ppm_color) < 1) usage(1); legacy = true; break; case 'v' : // -v (be verbose) Verbosity ++; break; default : // Unknown _cupsLangPrintf(stderr, _("%s: Unknown option \"-%c\"."), argv[0], *opt); usage(1); } } } else if (!name) { name = argv[i]; } else { _cupsLangPrintf(stderr, _("%s: Unknown option \"%s\"."), argv[0], argv[i]); usage(1); } } if (!name) usage(1); #if CUPS_LITE if (attrfile != NULL && legacy) usage(1); #else if (((ppdfile != NULL) + (attrfile != NULL) + legacy) > 1) usage(1); #endif /* CUPS_LITE */ // Apply defaults as needed... if (!directory[0]) { const char *tmpdir; // Temporary directory #ifdef _WIN32 if ((tmpdir = getenv("TEMP")) == NULL) tmpdir = "C:/TEMP"; #elif defined(__APPLE__) && TARGET_OS_OSX if ((tmpdir = getenv("TMPDIR")) == NULL) tmpdir = "/private/tmp"; #else if ((tmpdir = getenv("TMPDIR")) == NULL) tmpdir = "/tmp"; #endif // _WIN32 snprintf(directory, sizeof(directory), "%s/ippeveprinter.%d", tmpdir, (int)getpid()); if (mkdir(directory, 0755) && errno != EEXIST) { _cupsLangPrintf(stderr, _("Unable to create spool directory \"%s\": %s"), directory, strerror(errno)); usage(1); } if (Verbosity) _cupsLangPrintf(stderr, _("Using spool directory \"%s\"."), directory); } // Create the printer... if (attrfile) { attrs = load_ippfile_attributes(servername, serverport, attrfile, docformats); } #if !CUPS_LITE else if (ppdfile) { attrs = load_ppd_attributes(ppdfile, docformats); if (!command) command = "ippeveps"; if (!output_format) output_format = "application/postscript"; } #endif /* !CUPS_LITE */ else { attrs = load_legacy_attributes(make, model, ppm, ppm_color, duplex, docformats); } if (!docformats && !ippFindAttribute(attrs, "document-format-supported", IPP_TAG_MIMETYPE)) docformats = cupsArrayNewStrings(ppm_color > 0 ? "image/jpeg,image/pwg-raster,image/urf": "image/pwg-raster,image/urf", ','); if ((printer = create_printer(servername, serverport, name, location, icon, strings, docformats, subtypes, directory, command, device_uri, output_format, attrs)) == NULL) return (1); printer->web_forms = web_forms; cupsSetServerCredentials(keypath, printer->hostname, 1); // Run the print service... run_printer(printer); // Destroy the printer and exit... delete_printer(printer); return (0); } // // 'authenticate_request()' - Try to authenticate the request. // static http_status_t // O - HTTP_STATUS_CONTINUE to keep going, otherwise status to return authenticate_request( ippeve_client_t *client) // I - Client { #if HAVE_LIBPAM // If PAM isn't enabled, return 'continue' now... const char *authorization; // Pointer into Authorization string int userlen; // Username:password length pam_handle_t *pamh; // PAM authentication handle int pamerr; // PAM error code struct pam_conv pamdata; // PAM conversation data ippeve_authdata_t data; // Authentication data if (!PAMService) return (HTTP_STATUS_CONTINUE); // Try authenticating using PAM... authorization = httpGetField(client->http, HTTP_FIELD_AUTHORIZATION); if (!*authorization) return (HTTP_STATUS_UNAUTHORIZED); if (strncmp(authorization, "Basic ", 6)) { fputs("Unsupported scheme in Authorization header.\n", stderr); return (HTTP_STATUS_BAD_REQUEST); } authorization += 5; while (_cups_isspace(*authorization)) authorization ++; userlen = sizeof(data.username); httpDecode64_2(data.username, &userlen, authorization); if ((data.password = strchr(data.username, ':')) == NULL) { fputs("No password in Authorization header.\n", stderr); return (HTTP_STATUS_BAD_REQUEST); } *(data.password)++ = '\0'; if (!data.username[0]) { fputs("No username in Authorization header.\n", stderr); return (HTTP_STATUS_BAD_REQUEST); } pamdata.conv = pam_func; pamdata.appdata_ptr = &data; if ((pamerr = pam_start(PAMService, data.username, &pamdata, &pamh)) != PAM_SUCCESS) { fprintf(stderr, "pam_start() returned %d (%s)\n", pamerr, pam_strerror(pamh, pamerr)); return (HTTP_STATUS_SERVER_ERROR); } if ((pamerr = pam_authenticate(pamh, PAM_SILENT)) != PAM_SUCCESS) { fprintf(stderr, "pam_authenticate() returned %d (%s)\n", pamerr, pam_strerror(pamh, pamerr)); pam_end(pamh, 0); return (HTTP_STATUS_UNAUTHORIZED); } if ((pamerr = pam_acct_mgmt(pamh, PAM_SILENT)) != PAM_SUCCESS) { fprintf(stderr, "pam_acct_mgmt() returned %d (%s)\n", pamerr, pam_strerror(pamh, pamerr)); pam_end(pamh, 0); return (HTTP_STATUS_SERVER_ERROR); } cupsCopyString(client->username, data.username, sizeof(client->username)); pam_end(pamh, PAM_SUCCESS); return (HTTP_STATUS_CONTINUE); #else // No authentication support built-in, return 'continue'... (void)client; return (HTTP_STATUS_CONTINUE); #endif // HAVE_LIBPAM } // // 'clean_jobs()' - Clean out old (completed) jobs. // static void clean_jobs(ippeve_printer_t *printer) // I - Printer { ippeve_job_t *job; // Current job time_t cleantime; // Clean time if (cupsArrayGetCount(printer->jobs) == 0) return; cleantime = time(NULL) - 60; cupsRWLockWrite(&(printer->rwlock)); for (job = (ippeve_job_t *)cupsArrayGetFirst(printer->jobs); job; job = (ippeve_job_t *)cupsArrayGetNext(printer->jobs)) { if (job->completed && job->completed < cleantime) { cupsArrayRemove(printer->jobs, job); delete_job(job); } else { break; } } cupsRWUnlock(&(printer->rwlock)); } // // 'compare_jobs()' - Compare two jobs. // static int // O - Result of comparison compare_jobs(ippeve_job_t *a, // I - First job ippeve_job_t *b, // I - Second job void *data) // I - Callback data (unused) { (void)data; return (b->id - a->id); } // // 'copy_attributes()' - Copy attributes from one request to another. // static void copy_attributes(ipp_t *to, // I - Destination request ipp_t *from, // I - Source request cups_array_t *ra, // I - Requested attributes ipp_tag_t group_tag, // I - Group to copy bool quickcopy) // I - Do a quick copy? { ippeve_filter_t filter; // Filter data filter.ra = ra; filter.group_tag = group_tag; ippCopyAttributes(to, from, quickcopy, (ipp_copy_cb_t)filter_cb, &filter); } // // 'copy_job_attrs()' - Copy job attributes to the response. // static void copy_job_attributes( ippeve_client_t *client, // I - Client ippeve_job_t *job, // I - Job cups_array_t *ra) // I - requested-attributes { copy_attributes(client->response, job->attrs, ra, IPP_TAG_JOB, 0); if (!ra || cupsArrayFind(ra, "date-time-at-completed")) { if (job->completed) ippAddDate(client->response, IPP_TAG_JOB, "date-time-at-completed", ippTimeToDate(job->completed)); else ippAddOutOfBand(client->response, IPP_TAG_JOB, IPP_TAG_NOVALUE, "date-time-at-completed"); } if (!ra || cupsArrayFind(ra, "date-time-at-processing")) { if (job->processing) ippAddDate(client->response, IPP_TAG_JOB, "date-time-at-processing", ippTimeToDate(job->processing)); else ippAddOutOfBand(client->response, IPP_TAG_JOB, IPP_TAG_NOVALUE, "date-time-at-processing"); } if (!ra || cupsArrayFind(ra, "job-impressions")) ippAddInteger(client->response, IPP_TAG_JOB, IPP_TAG_INTEGER, "job-impressions", job->impressions); if (!ra || cupsArrayFind(ra, "job-impressions-completed")) ippAddInteger(client->response, IPP_TAG_JOB, IPP_TAG_INTEGER, "job-impressions-completed", job->impcompleted); if (!ra || cupsArrayFind(ra, "job-printer-up-time")) ippAddInteger(client->response, IPP_TAG_JOB, IPP_TAG_INTEGER, "job-printer-up-time", (int)(time(NULL) - client->printer->start_time)); if (!ra || cupsArrayFind(ra, "job-state")) ippAddInteger(client->response, IPP_TAG_JOB, IPP_TAG_ENUM, "job-state", (int)job->state); if (!ra || cupsArrayFind(ra, "job-state-message")) { if (job->message) { ippAddString(client->response, IPP_TAG_JOB, IPP_TAG_TEXT, "job-state-message", NULL, job->message); } else { switch (job->state) { case IPP_JSTATE_PENDING : ippAddString(client->response, IPP_TAG_JOB, IPP_CONST_TAG(IPP_TAG_TEXT), "job-state-message", NULL, "Job pending."); break; case IPP_JSTATE_HELD : if (job->fd >= 0) ippAddString(client->response, IPP_TAG_JOB, IPP_CONST_TAG(IPP_TAG_TEXT), "job-state-message", NULL, "Job incoming."); else if (ippFindAttribute(job->attrs, "job-hold-until", IPP_TAG_ZERO)) ippAddString(client->response, IPP_TAG_JOB, IPP_CONST_TAG(IPP_TAG_TEXT), "job-state-message", NULL, "Job held."); else ippAddString(client->response, IPP_TAG_JOB, IPP_CONST_TAG(IPP_TAG_TEXT), "job-state-message", NULL, "Job created."); break; case IPP_JSTATE_PROCESSING : if (job->cancel) ippAddString(client->response, IPP_TAG_JOB, IPP_CONST_TAG(IPP_TAG_TEXT), "job-state-message", NULL, "Job canceling."); else ippAddString(client->response, IPP_TAG_JOB, IPP_CONST_TAG(IPP_TAG_TEXT), "job-state-message", NULL, "Job printing."); break; case IPP_JSTATE_STOPPED : ippAddString(client->response, IPP_TAG_JOB, IPP_CONST_TAG(IPP_TAG_TEXT), "job-state-message", NULL, "Job stopped."); break; case IPP_JSTATE_CANCELED : ippAddString(client->response, IPP_TAG_JOB, IPP_CONST_TAG(IPP_TAG_TEXT), "job-state-message", NULL, "Job canceled."); break; case IPP_JSTATE_ABORTED : ippAddString(client->response, IPP_TAG_JOB, IPP_CONST_TAG(IPP_TAG_TEXT), "job-state-message", NULL, "Job aborted."); break; case IPP_JSTATE_COMPLETED : ippAddString(client->response, IPP_TAG_JOB, IPP_CONST_TAG(IPP_TAG_TEXT), "job-state-message", NULL, "Job completed."); break; } } } if (!ra || cupsArrayFind(ra, "job-state-reasons")) { switch (job->state) { case IPP_JSTATE_PENDING : ippAddString(client->response, IPP_TAG_JOB, IPP_CONST_TAG(IPP_TAG_KEYWORD), "job-state-reasons", NULL, "none"); break; case IPP_JSTATE_HELD : if (job->fd >= 0) ippAddString(client->response, IPP_TAG_JOB, IPP_CONST_TAG(IPP_TAG_KEYWORD), "job-state-reasons", NULL, "job-incoming"); else if (ippFindAttribute(job->attrs, "job-hold-until", IPP_TAG_ZERO)) ippAddString(client->response, IPP_TAG_JOB, IPP_CONST_TAG(IPP_TAG_KEYWORD), "job-state-reasons", NULL, "job-hold-until-specified"); else ippAddString(client->response, IPP_TAG_JOB, IPP_CONST_TAG(IPP_TAG_KEYWORD), "job-state-reasons", NULL, "job-data-insufficient"); break; case IPP_JSTATE_PROCESSING : if (job->cancel) ippAddString(client->response, IPP_TAG_JOB, IPP_CONST_TAG(IPP_TAG_KEYWORD), "job-state-reasons", NULL, "processing-to-stop-point"); else ippAddString(client->response, IPP_TAG_JOB, IPP_CONST_TAG(IPP_TAG_KEYWORD), "job-state-reasons", NULL, "job-printing"); break; case IPP_JSTATE_STOPPED : ippAddString(client->response, IPP_TAG_JOB, IPP_CONST_TAG(IPP_TAG_KEYWORD), "job-state-reasons", NULL, "job-stopped"); break; case IPP_JSTATE_CANCELED : ippAddString(client->response, IPP_TAG_JOB, IPP_CONST_TAG(IPP_TAG_KEYWORD), "job-state-reasons", NULL, "job-canceled-by-user"); break; case IPP_JSTATE_ABORTED : ippAddString(client->response, IPP_TAG_JOB, IPP_CONST_TAG(IPP_TAG_KEYWORD), "job-state-reasons", NULL, "aborted-by-system"); break; case IPP_JSTATE_COMPLETED : ippAddString(client->response, IPP_TAG_JOB, IPP_CONST_TAG(IPP_TAG_KEYWORD), "job-state-reasons", NULL, "job-completed-successfully"); break; } } if (!ra || cupsArrayFind(ra, "time-at-completed")) ippAddInteger(client->response, IPP_TAG_JOB, job->completed ? IPP_TAG_INTEGER : IPP_TAG_NOVALUE, "time-at-completed", (int)(job->completed - client->printer->start_time)); if (!ra || cupsArrayFind(ra, "time-at-processing")) ippAddInteger(client->response, IPP_TAG_JOB, job->processing ? IPP_TAG_INTEGER : IPP_TAG_NOVALUE, "time-at-processing", (int)(job->processing - client->printer->start_time)); } // // 'create_client()' - Accept a new network connection and create a client // object. // static ippeve_client_t * // O - Client create_client(ippeve_printer_t *printer, // I - Printer int sock) // I - Listen socket { ippeve_client_t *client; // Client if ((client = calloc(1, sizeof(ippeve_client_t))) == NULL) { perror("Unable to allocate memory for client"); return (NULL); } client->printer = printer; // Accept the client and get the remote address... if ((client->http = httpAcceptConnection(sock, 1)) == NULL) { perror("Unable to accept client connection"); free(client); return (NULL); } httpGetHostname(client->http, client->hostname, sizeof(client->hostname)); if (Verbosity) fprintf(stderr, "Accepted connection from %s\n", client->hostname); return (client); } // // 'create_job()' - Create a new job object from a Print-Job or Create-Job // request. // static ippeve_job_t * // O - Job create_job(ippeve_client_t *client) // I - Client { ippeve_job_t *job; // Job ipp_attribute_t *attr; // Job attribute char uri[1024], // job-uri value uuid[64]; // job-uuid value cupsRWLockWrite(&(client->printer->rwlock)); if (client->printer->active_job && client->printer->active_job->state < IPP_JSTATE_CANCELED) { // Only accept a single job at a time... cupsRWUnlock(&(client->printer->rwlock)); return (NULL); } // Allocate and initialize the job object... if ((job = calloc(1, sizeof(ippeve_job_t))) == NULL) { perror("Unable to allocate memory for job"); cupsRWUnlock(&(client->printer->rwlock)); return (NULL); } job->printer = client->printer; job->attrs = ippNew(); job->state = IPP_JSTATE_HELD; job->fd = -1; // Copy all of the job attributes... copy_attributes(job->attrs, client->request, NULL, IPP_TAG_JOB, 0); // Get the requesting-user-name, document format, and priority... if ((attr = ippFindAttribute(client->request, "requesting-user-name", IPP_TAG_NAME)) != NULL) job->username = ippGetString(attr, 0, NULL); else job->username = "anonymous"; ippAddString(job->attrs, IPP_TAG_JOB, IPP_TAG_NAME, "job-originating-user-name", NULL, job->username); if ((attr = ippFindAttribute(client->request, "requesting-user-uri", IPP_TAG_ZERO)) != NULL) { const char *user_uri = ippGetString(attr, 0, /*language*/NULL); // String value if (ippGetGroupTag(attr) == IPP_TAG_OPERATION && ippGetValueTag(attr) == IPP_TAG_URI && ippGetCount(attr) == 1 && user_uri && !strncmp(user_uri, "mailto:", 7)) ippAddString(job->attrs, IPP_TAG_JOB, IPP_TAG_URI, "job-originating-user-uri", NULL, user_uri); else respond_ignored(client, attr); } if ((attr = ippFindAttribute(client->request, "document-metadata", IPP_TAG_ZERO)) != NULL) { if (ippGetGroupTag(attr) == IPP_TAG_OPERATION && ippGetValueTag(attr) == IPP_TAG_STRING) { if ((attr = ippCopyAttribute(job->attrs, attr, /*quickcopy*/false)) != NULL) ippSetGroupTag(job->attrs, &attr, IPP_TAG_JOB); } else { respond_ignored(client, attr); } } if ((attr = ippFindAttribute(client->request, "document-password", IPP_TAG_ZERO)) != NULL) { if (ippGetGroupTag(attr) == IPP_TAG_OPERATION && ippGetValueTag(attr) == IPP_TAG_STRING && ippGetCount(attr) == 1) { void *dataptr; // octetString pointer int datalen; // Length of octetString if ((dataptr = ippGetOctetString(attr, 0, &datalen)) != NULL && datalen < (int)(sizeof(job->password) - 1)) memcpy(job->password, dataptr, datalen); else respond_ignored(client, attr); } else { respond_ignored(client, attr); } } if (ippGetOperation(client->request) != IPP_OP_CREATE_JOB) { if ((attr = ippFindAttribute(job->attrs, "document-format-detected", IPP_TAG_MIMETYPE)) != NULL) job->format = ippGetString(attr, 0, NULL); else if ((attr = ippFindAttribute(job->attrs, "document-format-supplied", IPP_TAG_MIMETYPE)) != NULL) job->format = ippGetString(attr, 0, NULL); else job->format = "application/octet-stream"; } if ((attr = ippFindAttribute(client->request, "job-impressions", IPP_TAG_INTEGER)) != NULL) job->impressions = ippGetInteger(attr, 0); if ((attr = ippFindAttribute(client->request, "job-name", IPP_TAG_NAME)) != NULL) job->name = ippGetString(attr, 0, NULL); // Add job description attributes and add to the jobs array... job->id = client->printer->next_job_id ++; if ((attr = ippFindAttribute(client->request, "printer-uri", IPP_TAG_URI)) != NULL) snprintf(uri, sizeof(uri), "%s/%d", ippGetString(attr, 0, NULL), job->id); else httpAssembleURIf(HTTP_URI_CODING_ALL, uri, sizeof(uri), "ipp", NULL, client->printer->hostname, client->printer->port, "/ipp/print/%d", job->id); httpAssembleUUID(client->printer->hostname, client->printer->port, client->printer->name, job->id, uuid, sizeof(uuid)); ippAddDate(job->attrs, IPP_TAG_JOB, "date-time-at-creation", ippTimeToDate(time(&job->created))); ippAddInteger(job->attrs, IPP_TAG_JOB, IPP_TAG_INTEGER, "job-id", job->id); ippAddString(job->attrs, IPP_TAG_JOB, IPP_TAG_URI, "job-uri", NULL, uri); ippAddString(job->attrs, IPP_TAG_JOB, IPP_TAG_URI, "job-uuid", NULL, uuid); if ((attr = ippFindAttribute(client->request, "printer-uri", IPP_TAG_URI)) != NULL) { ippAddString(job->attrs, IPP_TAG_JOB, IPP_TAG_URI, "job-printer-uri", NULL, ippGetString(attr, 0, NULL)); } else { char printer_uri[1024]; // job-printer-uri value httpAssembleURI(HTTP_URI_CODING_ALL, printer_uri, sizeof(printer_uri), "ipp", NULL, client->printer->hostname, client->printer->port, "/ipp/print"); ippAddString(job->attrs, IPP_TAG_JOB, IPP_TAG_URI, "job-printer-uri", NULL, printer_uri); } ippAddInteger(job->attrs, IPP_TAG_JOB, IPP_TAG_INTEGER, "time-at-creation", (int)(job->created - client->printer->start_time)); cupsArrayAdd(client->printer->jobs, job); client->printer->active_job = job; cupsRWUnlock(&(client->printer->rwlock)); return (job); } // // 'create_job_file()' - Create a file for the document in a job. // static int // O - File descriptor or -1 on error create_job_file( ippeve_job_t *job, // I - Job char *fname, // I - Filename buffer size_t fnamesize, // I - Size of filename buffer const char *directory, // I - Directory to store in const char *ext) // I - Extension (`NULL` for default) { char name[256], // "Safe" filename *nameptr; // Pointer into filename const char *job_name; // job-name value // Make a name from the job-name attribute... if ((job_name = ippGetString(ippFindAttribute(job->attrs, "job-name", IPP_TAG_NAME), 0, NULL)) == NULL) job_name = "untitled"; for (nameptr = name; *job_name && nameptr < (name + sizeof(name) - 1); job_name ++) { if (_cups_isalnum(*job_name) || *job_name == '-') { *nameptr++ = (char)_cups_tolower(*job_name); } else { *nameptr++ = '_'; while (job_name[1] && !_cups_isalnum(job_name[1]) && job_name[1] != '-') job_name ++; } } *nameptr = '\0'; // Figure out the extension... if (!ext) { if (!strcasecmp(job->format, "image/jpeg")) ext = "jpg"; else if (!strcasecmp(job->format, "image/png")) ext = "png"; else if (!strcasecmp(job->format, "image/pwg-raster")) ext = "pwg"; else if (!strcasecmp(job->format, "image/urf")) ext = "urf"; else if (!strcasecmp(job->format, "application/pdf")) ext = "pdf"; else if (!strcasecmp(job->format, "application/postscript")) ext = "ps"; else if (!strcasecmp(job->format, "application/vnd.hp-pcl")) ext = "pcl"; else ext = "dat"; } // Create a filename with the job-id, job-name, and document-format (extension)... snprintf(fname, fnamesize, "%s/%d-%s.%s", directory, job->id, name, ext); return (open(fname, O_WRONLY | O_CREAT | O_TRUNC | O_BINARY, 0666)); } // // 'create_listener()' - Create a listener socket. // static int // O - Listener socket or -1 on error create_listener(const char *name, // I - Host name (`NULL` for any address) int port, // I - Port number int family) // I - Address family { int sock; // Listener socket http_addrlist_t *addrlist; // Listen address char service[255]; // Service port snprintf(service, sizeof(service), "%d", port); if ((addrlist = httpAddrGetList(name, family, service)) == NULL) return (-1); sock = httpAddrListen(&(addrlist->addr), port); httpAddrFreeList(addrlist); return (sock); } // // 'create_media_col()' - Create a media-col value. // static ipp_t * // O - media-col collection create_media_col(const char *media, // I - Media name const char *source, // I - Media source, if any const char *type, // I - Media type, if any ipp_t *media_size,// I - media-size collection value int bottom, // I - Bottom margin in 2540ths int left, // I - Left margin in 2540ths int right, // I - Right margin in 2540ths int top) // I - Top margin in 2540ths { ipp_t *media_col = ippNew(); // media-col value char media_key[256]; // media-key value const char *media_key_suffix = ""; // media-key suffix if (bottom == 0 && left == 0 && right == 0 && top == 0) media_key_suffix = "_borderless"; if (media) { if (type && source) snprintf(media_key, sizeof(media_key), "%s_%s_%s%s", media, source, type, media_key_suffix); else if (type) snprintf(media_key, sizeof(media_key), "%s__%s%s", media, type, media_key_suffix); else if (source) snprintf(media_key, sizeof(media_key), "%s_%s%s", media, source, media_key_suffix); else snprintf(media_key, sizeof(media_key), "%s%s", media, media_key_suffix); ippAddString(media_col, IPP_TAG_PRINTER, IPP_TAG_KEYWORD, "media-key", NULL, media_key); } ippAddCollection(media_col, IPP_TAG_PRINTER, "media-size", media_size); if (media) ippAddString(media_col, IPP_TAG_PRINTER, IPP_TAG_KEYWORD, "media-size-name", NULL, media); if (bottom >= 0) ippAddInteger(media_col, IPP_TAG_PRINTER, IPP_TAG_INTEGER, "media-bottom-margin", bottom); if (left >= 0) ippAddInteger(media_col, IPP_TAG_PRINTER, IPP_TAG_INTEGER, "media-left-margin", left); if (right >= 0) ippAddInteger(media_col, IPP_TAG_PRINTER, IPP_TAG_INTEGER, "media-right-margin", right); if (top >= 0) ippAddInteger(media_col, IPP_TAG_PRINTER, IPP_TAG_INTEGER, "media-top-margin", top); if (source) ippAddString(media_col, IPP_TAG_PRINTER, IPP_TAG_KEYWORD, "media-source", NULL, source); if (type) ippAddString(media_col, IPP_TAG_PRINTER, IPP_TAG_KEYWORD, "media-type", NULL, type); ippDelete(media_size); return (media_col); } // // 'create_media_size()' - Create a media-size value. // static ipp_t * // O - media-col collection create_media_size(int width, // I - x-dimension in 2540ths int length) // I - y-dimension in 2540ths { ipp_t *media_size = ippNew(); // media-size value ippAddInteger(media_size, IPP_TAG_ZERO, IPP_TAG_INTEGER, "x-dimension", width); ippAddInteger(media_size, IPP_TAG_ZERO, IPP_TAG_INTEGER, "y-dimension", length); return (media_size); } // // 'create_media_size_range()' - Create a ranged media-size value. // static ipp_t * // O - media-col collection create_media_size_range(int min_width, // I - Minimum x-dimension in 2540ths int max_width, // I - Maximum x-dimension in 2540ths int min_length, // I - Minimum x-dimension in 2540ths int max_length) // I - Maximum y-dimension in 2540ths { ipp_t *media_size = ippNew(); // media-size value ippAddRange(media_size, IPP_TAG_ZERO, "x-dimension", min_width, max_width); ippAddRange(media_size, IPP_TAG_ZERO, "y-dimension", min_length, max_length); return (media_size); } // // 'create_printer()' - Create, register, and listen for connections to a // printer object. // static ippeve_printer_t * // O - Printer create_printer( const char *servername, // I - Server hostname (NULL for default) int serverport, // I - Server port const char *name, // I - printer-name const char *location, // I - printer-location const char *icons, // I - printer-icons const char *strings, // I - printer-strings-uri cups_array_t *docformats, // I - document-format-supported const char *subtypes, // I - DNS-SD service subtype(s) const char *directory, // I - Spool directory const char *command, // I - Command to run on job files, if any const char *device_uri, // I - Output device, if any const char *output_format, // I - Output format, if any ipp_t *attrs) // I - Capability attributes { ippeve_printer_t *printer; // Printer int i; // Looping var ipp_attribute_t *attr; // Current attribute #ifndef _WIN32 char path[1024]; // Full path to command #endif // !_WIN32 unsigned char sha256[32]; // SHA-256 digest/sum char uuid_data[1024],// Data to hash for printer-uuid uuid[128], // printer-uuid *iconsptr; // Pointer into icons string int k_supported; // Maximum file size supported int num_formats = 0;// Number of supported document formats const char *formats[100], // Supported document formats *format; // Current format bool has_jpeg = false, // Supports JPEG? has_pdf = false;// Supports PDF? size_t num_sup_attrs; // Number of supported attributes const char *sup_attrs[100];// Supported attributes char xxx_supported[256]; // Name of -supported attribute #ifdef HAVE_STATVFS struct statvfs spoolinfo; // FS info for spool directory double spoolsize; // FS size #elif defined(HAVE_STATFS) struct statfs spoolinfo; // FS info for spool directory double spoolsize; // FS size #endif // HAVE_STATVFS static const char * const versions[] =// ipp-versions-supported values { "1.1", "2.0" }; static const char * const features[] =// ipp-features-supported values { "ipp-everywhere" }; static const int ops[] = // operations-supported values { IPP_OP_PRINT_JOB, IPP_OP_PRINT_URI, IPP_OP_VALIDATE_JOB, IPP_OP_CREATE_JOB, IPP_OP_SEND_DOCUMENT, IPP_OP_SEND_URI, IPP_OP_CANCEL_JOB, IPP_OP_GET_JOB_ATTRIBUTES, IPP_OP_GET_JOBS, IPP_OP_GET_PRINTER_ATTRIBUTES, IPP_OP_CANCEL_JOBS, IPP_OP_CANCEL_MY_JOBS, IPP_OP_CLOSE_JOB, IPP_OP_IDENTIFY_PRINTER }; static const char * const charsets[] =// charset-supported values { "us-ascii", "utf-8" }; static const char * const compressions[] =// compression-supported values { "deflate", "gzip", "none" }; static const char * const identify_actions[] = { "display", "sound" }; static const char * const job_creation[] = { // job-creation-attributes-supported values "copies", "document-access", "document-charset", "document-format", "document-message", "document-metadata", "document-name", "document-natural-language", "document-password", "finishings", "finishings-col", "ipp-attribute-fidelity", "job-account-id", "job-account-type", "job-accouunting-sheets", "job-accounting-user-id", "job-authorization-uri", "job-error-action", "job-error-sheet", "job-hold-until", "job-hold-until-time", "job-mandatory-attributes", "job-message-to-operator", "job-name", "job-pages-per-set", "job-password", "job-password-encryption", "job-phone-number", "job-priority", "job-recipient-name", "job-resource-ids", "job-sheet-message", "job-sheets", "job-sheets-col", "media", "media-col", "multiple-document-handling", "number-up", "orientation-requested", "output-bin", "output-device", "overrides", "page-delivery", "page-ranges", "presentation-direction-number-up", "print-color-mode", "print-content-optimize", "print-quality", "print-rendering-intent", "print-scaling", "printer-resolution", "proof-print", "separator-sheets", "sides", "x-image-position", "x-image-shift", "x-side1-image-shift", "x-side2-image-shift", "y-image-position", "y-image-shift", "y-side1-image-shift", "y-side2-image-shift" }; static const char * const job_history[] = { // job-history-attributes-configured/supported "date-time-at-creation", "date-time-at-processing", "date-time-at-completed", "job-id", "job-name", "job-originating-user-name", "job-originating-user-uri", "job-printer-uri", "job-state", "job-state-reasons", "job-uri", "time-at-creation", "time-at-processing", "time-at-completed" }; static const char * const media_col_supported[] = { // media-col-supported values "media-bottom-margin", "media-left-margin", "media-right-margin", "media-size", "media-size-name", "media-source", "media-top-margin", "media-type" }; static const char * const multiple_document_handling[] = { // multiple-document-handling-supported values "separate-documents-uncollated-copies", "separate-documents-collated-copies" }; static const char * const reference_uri_schemes_supported[] = { // reference-uri-schemes-supported "file", "ftp", "http", "https" }; static const char * const uri_authentication_supported[] = { // uri-authentication-supported values "none", "none" }; static const char * const uri_authentication_basic[] = { // uri-authentication-supported values with authentication "basic", "basic" }; static const char * const uri_security_supported[] = { // uri-security-supported values "none", "tls" }; static const char * const which_jobs[] = { // which-jobs-supported values "completed", "not-completed", "aborted", "all", "canceled", "pending", "pending-held", "processing", "processing-stopped" }; #ifndef _WIN32 // If a command was specified, make sure it exists and is executable... if (command && access(command, X_OK)) { if (cupsFileFind(command, getenv("PATH"), true, path, sizeof(path))) { // Found the command in the current path... command = path; } else { // No command... _cupsLangPrintf(stderr, _("Unable to execute command \"%s\": %s"), command, strerror(errno)); return (NULL); } } #endif // !_WIN32 // Allocate memory for the printer... if ((printer = calloc(1, sizeof(ippeve_printer_t))) == NULL) { _cupsLangPrintf(stderr, _("Unable to allocate memory for printer: %s"), strerror(errno)); return (NULL); } printer->ipv4 = -1; printer->ipv6 = -1; printer->name = strdup(name); printer->dnssd = cupsDNSSDNew(NULL, NULL); printer->dnssd_name = strdup(name); printer->dnssd_subtypes = subtypes ? strdup(subtypes) : NULL; printer->command = command ? strdup(command) : NULL; printer->device_uri = device_uri ? strdup(device_uri) : NULL; printer->output_format = output_format ? strdup(output_format) : NULL; printer->directory = strdup(directory); printer->icons[0] = icons ? strdup(icons) : NULL; printer->strings = strings ? strdup(strings) : NULL; printer->port = serverport; printer->start_time = time(NULL); printer->config_time = printer->start_time; printer->state = IPP_PSTATE_IDLE; printer->state_reasons = IPPEVE_PREASON_NONE; printer->state_time = printer->start_time; printer->jobs = cupsArrayNew3((cups_array_cb_t)compare_jobs, NULL, NULL, 0, NULL, NULL); printer->next_job_id = 1; if (printer->icons[0]) { // Extract up to 3 icons... for (i = 1, iconsptr = strchr(printer->icons[0], ','); iconsptr && i < 3; i ++, iconsptr = strchr(iconsptr, ',')) { *iconsptr++ = '\0'; printer->icons[i] = iconsptr; } if (iconsptr) *iconsptr = '\0'; // Strip any icons after the third... while (i < 3) { printer->icons[i] = printer->icons[i - 1]; i ++; } } if (servername) { printer->hostname = strdup(servername); } else { char temp[1024]; // Temporary string printer->hostname = strdup(cupsDNSSDCopyHostName(printer->dnssd, temp, sizeof(temp))); } cupsRWInit(&(printer->rwlock)); // Create the listener sockets... if (printer->port) { if ((printer->ipv4 = create_listener(servername, printer->port, AF_INET)) < 0) { perror("Unable to create IPv4 listener"); goto bad_printer; } } else { #ifdef _WIN32 // Windows is almost always used as a single user system, so use a default // port number of 8631. serverport = 8631; #else // Use 8000 + UID mod 1000 for the default port number... serverport = 8000 + ((int)getuid() % 1000); #endif // _WIN32 while (serverport < 10000) { if ((printer->ipv4 = create_listener(servername, serverport, AF_INET)) >= 0) break; serverport ++; } if (serverport < 10000) { _cupsLangPrintf(stderr, _("Listening on port %d."), serverport); printer->port = serverport; } else { perror("Unable to create IPv4 listener"); goto bad_printer; } } if ((printer->ipv6 = create_listener(servername, printer->port, AF_INET6)) < 0) { perror("Unable to create IPv6 listener"); goto bad_printer; } // Prepare values for the printer attributes... snprintf(uuid_data, sizeof(uuid_data), "_IPPEVEPRINTER_:%s:%d:%s", printer->hostname, printer->port, printer->name); cupsHashData("sha2-256", (unsigned char *)uuid_data, strlen(uuid_data), sha256, sizeof(sha256)); snprintf(uuid, sizeof(uuid), "urn:uuid:%02x%02x%02x%02x-%02x%02x-%02x%02x-%02x%02x-%02x%02x%02x%02x%02x%02x", sha256[0], sha256[1], sha256[3], sha256[4], sha256[5], sha256[6], (sha256[10] & 15) | 0x30, sha256[11], (sha256[15] & 0x3f) | 0x40, sha256[16], sha256[20], sha256[21], sha256[25], sha256[26], sha256[30], sha256[31]); if (Verbosity) { fprintf(stderr, "printer-uri-supported=\"ipp://%s:%d/ipp/print\",\"ipps://%s:%d/ipp/print\"\n", printer->hostname, printer->port, printer->hostname, printer->port); fprintf(stderr, "printer-uuid=\"%s\"\n", uuid); } // Get the maximum spool size based on the size of the filesystem used for // the spool directory. If the host OS doesn't support the statfs call // or the filesystem is larger than 2TiB, always report INT_MAX. #ifdef HAVE_STATVFS if (statvfs(printer->directory, &spoolinfo)) k_supported = INT_MAX; else if ((spoolsize = (double)spoolinfo.f_frsize * spoolinfo.f_blocks / 1024) > INT_MAX) k_supported = INT_MAX; else k_supported = (int)spoolsize; #elif defined(HAVE_STATFS) if (statfs(printer->directory, &spoolinfo)) k_supported = INT_MAX; else if ((spoolsize = (double)spoolinfo.f_bsize * spoolinfo.f_blocks / 1024) > INT_MAX) k_supported = INT_MAX; else k_supported = (int)spoolsize; #else k_supported = INT_MAX; #endif // HAVE_STATVFS // Assemble the final list of document formats... if (docformats) { if (!cupsArrayFind(docformats, (void *)"application/octet-stream")) cupsArrayAdd(docformats, (void *)"application/octet-stream"); for (format = (const char *)cupsArrayGetFirst(docformats); format && num_formats < (int)(sizeof(formats) / sizeof(formats[0])); format = (const char *)cupsArrayGetNext(docformats)) formats[num_formats ++] = format; } else if ((attr = ippFindAttribute(attrs, "document-format-supported", IPP_TAG_MIMETYPE)) != NULL) { int count = ippGetCount(attr); // Number of formats for (i = 0; i < count && i < (int)(sizeof(formats) / sizeof(formats[0])); i ++) formats[i] = ippGetString(attr, i, /*language*/NULL); num_formats = i; } for (i = 0; i < num_formats; i ++) { if (!strcasecmp(formats[i], "image/jpeg")) has_jpeg = true; else if (!strcasecmp(formats[i], "application/pdf")) has_pdf = true; } // Get the list of attributes that can be used when creating a job... num_sup_attrs = 0; sup_attrs[num_sup_attrs ++] = "document-access"; sup_attrs[num_sup_attrs ++] = "document-charset"; sup_attrs[num_sup_attrs ++] = "document-format"; sup_attrs[num_sup_attrs ++] = "document-message"; sup_attrs[num_sup_attrs ++] = "document-metadata"; sup_attrs[num_sup_attrs ++] = "document-name"; sup_attrs[num_sup_attrs ++] = "document-natural-language"; if (has_pdf) sup_attrs[num_sup_attrs ++] = "document-password"; sup_attrs[num_sup_attrs ++] = "ipp-attribute-fidelity"; sup_attrs[num_sup_attrs ++] = "job-name"; sup_attrs[num_sup_attrs ++] = "job-priority"; for (i = 0; i < (int)(sizeof(job_creation) / sizeof(job_creation[0])) && num_sup_attrs < (int)(sizeof(sup_attrs) / sizeof(sup_attrs[0])); i ++) { snprintf(xxx_supported, sizeof(xxx_supported), "%s-supported", job_creation[i]); if (ippFindAttribute(attrs, xxx_supported, IPP_TAG_ZERO)) sup_attrs[num_sup_attrs ++] = job_creation[i]; } // Fill out the rest of the printer attributes. printer->attrs = attrs; // charset-configured ippAddString(printer->attrs, IPP_TAG_PRINTER, IPP_CONST_TAG(IPP_TAG_CHARSET), "charset-configured", NULL, "utf-8"); // charset-supported ippAddStrings(printer->attrs, IPP_TAG_PRINTER, IPP_CONST_TAG(IPP_TAG_CHARSET), "charset-supported", sizeof(charsets) / sizeof(charsets[0]), NULL, charsets); // compression-supported if (!ippFindAttribute(printer->attrs, "compression-supported", IPP_TAG_ZERO)) ippAddStrings(printer->attrs, IPP_TAG_PRINTER, IPP_CONST_TAG(IPP_TAG_KEYWORD), "compression-supported", (int)(sizeof(compressions) / sizeof(compressions[0])), NULL, compressions); // device-uuid ippAddString(printer->attrs, IPP_TAG_PRINTER, IPP_TAG_URI, "device-uuid", /*language*/NULL, uuid); if (docformats) { // document-format-default if (!ippFindAttribute(printer->attrs, "document-format-default", IPP_TAG_MIMETYPE)) ippAddString(printer->attrs, IPP_TAG_PRINTER, IPP_CONST_TAG(IPP_TAG_MIMETYPE), "document-format-default", NULL, "application/octet-stream"); // document-format-supported if ((attr = ippFindAttribute(printer->attrs, "document-format-supported", IPP_TAG_MIMETYPE)) != NULL) ippDeleteAttribute(printer->attrs, attr); ippAddStrings(printer->attrs, IPP_TAG_PRINTER, IPP_TAG_MIMETYPE, "document-format-supported", num_formats, NULL, formats); } // generated-natural-language-supported ippAddString(printer->attrs, IPP_TAG_PRINTER, IPP_CONST_TAG(IPP_TAG_LANGUAGE), "generated-natural-language-supported", NULL, "en"); // identify-actions-default ippAddString (printer->attrs, IPP_TAG_PRINTER, IPP_CONST_TAG(IPP_TAG_KEYWORD), "identify-actions-default", NULL, "sound"); // identify-actions-supported ippAddStrings(printer->attrs, IPP_TAG_PRINTER, IPP_CONST_TAG(IPP_TAG_KEYWORD), "identify-actions-supported", sizeof(identify_actions) / sizeof(identify_actions[0]), NULL, identify_actions); // ipp-features-supported ippAddStrings(printer->attrs, IPP_TAG_PRINTER, IPP_CONST_TAG(IPP_TAG_KEYWORD), "ipp-features-supported", sizeof(features) / sizeof(features[0]), NULL, features); // ipp-versions-supported if (MaxVersion == 11) ippAddString(printer->attrs, IPP_TAG_PRINTER, IPP_CONST_TAG(IPP_TAG_KEYWORD), "ipp-versions-supported", NULL, "1.1"); else ippAddStrings(printer->attrs, IPP_TAG_PRINTER, IPP_CONST_TAG(IPP_TAG_KEYWORD), "ipp-versions-supported", (int)(sizeof(versions) / sizeof(versions[0])), NULL, versions); // job-creation-attributes-supported ippAddStrings(printer->attrs, IPP_TAG_PRINTER, IPP_CONST_TAG(IPP_TAG_KEYWORD), "job-creation-attributes-supported", num_sup_attrs, NULL, sup_attrs); // job-history-attributes-configured ippAddStrings(printer->attrs, IPP_TAG_PRINTER, IPP_CONST_TAG(IPP_TAG_KEYWORD), "job-history-attributes-configured", sizeof(job_history) / sizeof(job_history[0]), /*language*/NULL, job_history); // job-history-attributes-supported ippAddStrings(printer->attrs, IPP_TAG_PRINTER, IPP_CONST_TAG(IPP_TAG_KEYWORD), "job-history-attributes-supported", sizeof(job_history) / sizeof(job_history[0]), /*language*/NULL, job_history); // job-history-interval-configured ippAddInteger(printer->attrs, IPP_TAG_PRINTER, IPP_TAG_INTEGER, "job-history-interval-configured", 60); // job-history-interval-supported ippAddRange(printer->attrs, IPP_TAG_PRINTER, "job-history-interval-supported", 0, 3600); // job-ids-supported ippAddBoolean(printer->attrs, IPP_TAG_PRINTER, "job-ids-supported", 1); // job-k-octets-supported ippAddRange(printer->attrs, IPP_TAG_PRINTER, "job-k-octets-supported", 0, k_supported); // job-mandatory-attributes-supported ippAddBoolean(printer->attrs, IPP_TAG_PRINTER, "job-mandatory-attributes-supported", true); // job-priority-default ippAddInteger(printer->attrs, IPP_TAG_PRINTER, IPP_TAG_INTEGER, "job-priority-default", 50); // job-priority-supported ippAddInteger(printer->attrs, IPP_TAG_PRINTER, IPP_TAG_INTEGER, "job-priority-supported", 1); // job-sheets-default // ippAddString(printer->attrs, IPP_TAG_PRINTER, IPP_CONST_TAG(IPP_TAG_NAME), "job-sheets-default", NULL, "none"); // job-sheets-supported // ippAddString(printer->attrs, IPP_TAG_PRINTER, IPP_CONST_TAG(IPP_TAG_NAME), "job-sheets-supported", NULL, "none"); // job-spooling-supported ippAddString(printer->attrs, IPP_TAG_PRINTER, IPP_CONST_TAG(IPP_TAG_KEYWORD), "job-spooling-supported", /*language*/NULL, "stream"); if (has_jpeg) { // jpeg-features-supported if (!ippFindAttribute(printer->attrs, "jpeg-features-supported", IPP_TAG_ZERO)) ippAddString(printer->attrs, IPP_TAG_PRINTER, IPP_CONST_TAG(IPP_TAG_KEYWORD), "jpeg-features-supported", /*language*/NULL, "none"); // jpeg-k-octets-supported if (!ippFindAttribute(printer->attrs, "jpeg-k-octets-supported", IPP_TAG_ZERO)) ippAddRange(printer->attrs, IPP_TAG_PRINTER, "jpeg-k-octets-supported", 0, k_supported); // jpeg-x-dimension-supported if (!ippFindAttribute(printer->attrs, "jpeg-x-dimension-supported", IPP_TAG_ZERO)) ippAddRange(printer->attrs, IPP_TAG_PRINTER, "jpeg-x-dimension-supported", 0, 16384); // jpeg-y-dimension-supported if (!ippFindAttribute(printer->attrs, "jpeg-y-dimension-supported", IPP_TAG_ZERO)) ippAddRange(printer->attrs, IPP_TAG_PRINTER, "jpeg-y-dimension-supported", 1, 16384); } // media-col-supported ippAddStrings(printer->attrs, IPP_TAG_PRINTER, IPP_CONST_TAG(IPP_TAG_KEYWORD), "media-col-supported", (int)(sizeof(media_col_supported) / sizeof(media_col_supported[0])), NULL, media_col_supported); // multiple-document-handling-default if (!ippFindAttribute(printer->attrs, "multiple-document-handling-default", IPP_TAG_ZERO)) ippAddString(printer->attrs, IPP_TAG_PRINTER, IPP_CONST_TAG(IPP_TAG_KEYWORD), "multiple-document-handling-default", NULL, "separate-documents-collated-copies"); // multiple-document-handling-supported ippAddStrings(printer->attrs, IPP_TAG_PRINTER, IPP_CONST_TAG(IPP_TAG_KEYWORD), "multiple-document-handling-supported", sizeof(multiple_document_handling) / sizeof(multiple_document_handling[0]), NULL, multiple_document_handling); // multiple-document-jobs-supported ippAddBoolean(printer->attrs, IPP_TAG_PRINTER, "multiple-document-jobs-supported", 0); // multiple-operation-time-out ippAddInteger(printer->attrs, IPP_TAG_PRINTER, IPP_TAG_INTEGER, "multiple-operation-time-out", 60); // multiple-operation-time-out-action ippAddString(printer->attrs, IPP_TAG_PRINTER, IPP_CONST_TAG(IPP_TAG_KEYWORD), "multiple-operation-time-out-action", NULL, "abort-job"); // natural-language-configured ippAddString(printer->attrs, IPP_TAG_PRINTER, IPP_CONST_TAG(IPP_TAG_LANGUAGE), "natural-language-configured", NULL, "en"); // operations-supported ippAddIntegers(printer->attrs, IPP_TAG_PRINTER, IPP_TAG_ENUM, "operations-supported", sizeof(ops) / sizeof(ops[0]), ops); if (has_pdf) { // document-password-supported if (!ippFindAttribute(printer->attrs, "document-password-supported", IPP_TAG_ZERO)) ippAddInteger(printer->attrs, IPP_TAG_PRINTER, IPP_TAG_INTEGER, "document-password-supported", 255); // pdf-k-octets-supported if (!ippFindAttribute(printer->attrs, "pdf-k-octets-supported", IPP_TAG_ZERO)) ippAddRange(printer->attrs, IPP_TAG_PRINTER, "pdf-k-octets-supported", 0, k_supported); // pdf-versions-supported if (!ippFindAttribute(printer->attrs, "pdf-versions-supported", IPP_TAG_ZERO)) ippAddString(printer->attrs, IPP_TAG_PRINTER, IPP_CONST_TAG(IPP_TAG_KEYWORD), "pdf-versions-supported", /*language*/NULL, "iso-32000-2_2017"); } // pdl-override-supported ippAddString(printer->attrs, IPP_TAG_PRINTER, IPP_CONST_TAG(IPP_TAG_KEYWORD), "pdl-override-supported", NULL, "attempted"); // preferred-attributes-supported ippAddBoolean(printer->attrs, IPP_TAG_PRINTER, "preferred-attributes-supported", 0); // print-processing-attributes-supported if (!ippFindAttribute(printer->attrs, "print-processing-attributes-supported", IPP_TAG_ZERO)) { num_sup_attrs = 0; if (ippFindAttribute(printer->attrs, "media-overprint-supported", IPP_TAG_ZERO)) sup_attrs[num_sup_attrs ++] = "media-overprint"; if (ippFindAttribute(printer->attrs, "print-color-mode-supported", IPP_TAG_ZERO)) sup_attrs[num_sup_attrs ++] = "print-color-mode"; if (ippFindAttribute(printer->attrs, "print-content-optimize-supported", IPP_TAG_ZERO)) sup_attrs[num_sup_attrs ++] = "print-content-optimize"; if (ippFindAttribute(printer->attrs, "print-darkness-supported", IPP_TAG_ZERO)) sup_attrs[num_sup_attrs ++] = "print-darkness"; if (ippFindAttribute(printer->attrs, "print-rendering-intent-supported", IPP_TAG_ZERO)) sup_attrs[num_sup_attrs ++] = "print-rendering-intent"; if (ippFindAttribute(printer->attrs, "print-quality-supported", IPP_TAG_ZERO)) sup_attrs[num_sup_attrs ++] = "print-quality"; if (ippFindAttribute(printer->attrs, "print-speed-supported", IPP_TAG_ZERO)) sup_attrs[num_sup_attrs ++] = "print-speed"; if (ippFindAttribute(printer->attrs, "printer-resolution-supported", IPP_TAG_ZERO)) sup_attrs[num_sup_attrs ++] = "printer-resolution"; if (num_sup_attrs > 0) ippAddStrings(printer->attrs, IPP_TAG_PRINTER, IPP_CONST_TAG(IPP_TAG_KEYWORD), "print-processing-attributes-supported", num_sup_attrs, /*language*/NULL, sup_attrs); } // print-scaling-default if (!ippFindAttribute(printer->attrs, "print-scaling-default", IPP_TAG_ZERO)) ippAddString(printer->attrs, IPP_TAG_PRINTER, IPP_CONST_TAG(IPP_TAG_KEYWORD), "print-scaling-default", /*language*/NULL, "none"); // print-scaling-supported if (!ippFindAttribute(printer->attrs, "print-scaling-supported", IPP_TAG_ZERO)) ippAddString(printer->attrs, IPP_TAG_PRINTER, IPP_CONST_TAG(IPP_TAG_KEYWORD), "print-scaling-supported", /*language*/NULL, "none"); // printer-get-attributes-supported ippAddString(printer->attrs, IPP_TAG_PRINTER, IPP_CONST_TAG(IPP_TAG_KEYWORD), "printer-get-attributes-supported", NULL, "document-format"); // printer-geo-location ippAddOutOfBand(printer->attrs, IPP_TAG_PRINTER, IPP_TAG_UNKNOWN, "printer-geo-location"); // printer-is-accepting-jobs ippAddBoolean(printer->attrs, IPP_TAG_PRINTER, "printer-is-accepting-jobs", 1); // printer-info ippAddString(printer->attrs, IPP_TAG_PRINTER, IPP_TAG_TEXT, "printer-info", NULL, name); // printer-kind if (!ippFindAttribute(printer->attrs, "printer-kind", IPP_TAG_ZERO)) ippAddString(printer->attrs, IPP_TAG_PRINTER, IPP_CONST_TAG(IPP_TAG_KEYWORD), "printer-kind", /*language*/NULL, "document"); // printer-location ippAddString(printer->attrs, IPP_TAG_PRINTER, IPP_TAG_TEXT, "printer-location", NULL, location); // printer-name ippAddString(printer->attrs, IPP_TAG_PRINTER, IPP_TAG_NAME, "printer-name", NULL, name); // printer-organization ippAddString(printer->attrs, IPP_TAG_PRINTER, IPP_CONST_TAG(IPP_TAG_TEXT), "printer-organization", NULL, ""); // printer-organizational-unit ippAddString(printer->attrs, IPP_TAG_PRINTER, IPP_CONST_TAG(IPP_TAG_TEXT), "printer-organizational-unit", NULL, ""); // printer-strings-languages-supported ippAddString(printer->attrs, IPP_TAG_PRINTER, IPP_TAG_LANGUAGE, "printer-strings-languages-supported", NULL, "en"); // printer-uuid ippAddString(printer->attrs, IPP_TAG_PRINTER, IPP_TAG_URI, "printer-uuid", NULL, uuid); // reference-uri-schemes-supported ippAddStrings(printer->attrs, IPP_TAG_PRINTER, IPP_CONST_TAG(IPP_TAG_URISCHEME), "reference-uri-schemes-supported", (int)(sizeof(reference_uri_schemes_supported) / sizeof(reference_uri_schemes_supported[0])), NULL, reference_uri_schemes_supported); // requesting-user-uri-supported ippAddBoolean(printer->attrs, IPP_TAG_PRINTER, "requesting-user-uri-supported", 1); // requesting-user-uri-scheme-supported ippAddString(printer->attrs, IPP_TAG_PRINTER, IPP_TAG_URISCHEME, "requesting-user-uri-schemes-supported", NULL, "mailto"); // uri-authentication-supported if (PAMService) ippAddStrings(printer->attrs, IPP_TAG_PRINTER, IPP_CONST_TAG(IPP_TAG_KEYWORD), "uri-authentication-supported", 2, NULL, uri_authentication_basic); else ippAddStrings(printer->attrs, IPP_TAG_PRINTER, IPP_CONST_TAG(IPP_TAG_KEYWORD), "uri-authentication-supported", 2, NULL, uri_authentication_supported); // uri-security-supported ippAddStrings(printer->attrs, IPP_TAG_PRINTER, IPP_CONST_TAG(IPP_TAG_KEYWORD), "uri-security-supported", 2, NULL, uri_security_supported); // which-jobs-supported ippAddStrings(printer->attrs, IPP_TAG_PRINTER, IPP_CONST_TAG(IPP_TAG_KEYWORD), "which-jobs-supported", sizeof(which_jobs) / sizeof(which_jobs[0]), NULL, which_jobs); debug_attributes("Printer", printer->attrs, 0); // Register the printer with DNS-SD... if (!register_printer(printer)) goto bad_printer; // Return it! return (printer); // If we get here we were unable to create the printer... bad_printer: delete_printer(printer); return (NULL); } // // 'debug_attributes()' - Print attributes in a request or response. // static void debug_attributes(const char *title, // I - Title ipp_t *ipp, // I - Request/response int type) // I - 0 = object, 1 = request, 2 = response { ipp_tag_t group_tag; // Current group ipp_attribute_t *attr; // Current attribute char buffer[2048]; // String buffer for value int major, minor; // Version if (Verbosity <= 1) return; fprintf(stderr, "%s:\n", title); major = ippGetVersion(ipp, &minor); fprintf(stderr, " version=%d.%d\n", major, minor); if (type == 1) fprintf(stderr, " operation-id=%s(%04x)\n", ippOpString(ippGetOperation(ipp)), ippGetOperation(ipp)); else if (type == 2) fprintf(stderr, " status-code=%s(%04x)\n", ippErrorString(ippGetStatusCode(ipp)), ippGetStatusCode(ipp)); fprintf(stderr, " request-id=%d\n\n", ippGetRequestId(ipp)); for (attr = ippGetFirstAttribute(ipp), group_tag = IPP_TAG_ZERO; attr; attr = ippGetNextAttribute(ipp)) { if (ippGetGroupTag(attr) != group_tag) { group_tag = ippGetGroupTag(attr); fprintf(stderr, " %s\n", ippTagString(group_tag)); } if (ippGetName(attr)) { ippAttributeString(attr, buffer, sizeof(buffer)); fprintf(stderr, " %s (%s%s) %s\n", ippGetName(attr), ippGetCount(attr) > 1 ? "1setOf " : "", ippTagString(ippGetValueTag(attr)), buffer); } } } // // 'delete_client()' - Close the socket and free all memory used by a client // object. // static void delete_client(ippeve_client_t *client) // I - Client { if (Verbosity) fprintf(stderr, "Closing connection from %s\n", client->hostname); // Flush pending writes before closing... httpFlushWrite(client->http); // Free memory... httpClose(client->http); ippDelete(client->request); ippDelete(client->response); free(client); } // // 'delete_job()' - Remove from the printer and free all memory used by a job // object. // static void delete_job(ippeve_job_t *job) // I - Job { if (Verbosity) fprintf(stderr, "[Job %d] Removing job from history.\n", job->id); ippDelete(job->attrs); free(job->message); if (job->filename) { if (!KeepFiles) unlink(job->filename); free(job->filename); } free(job); } // // 'delete_printer()' - Unregister, close listen sockets, and free all memory // used by a printer object. // static void delete_printer( ippeve_printer_t *printer) // I - Printer { if (printer->ipv4 >= 0) httpAddrClose(/*addr*/NULL, printer->ipv4); if (printer->ipv6 >= 0) httpAddrClose(/*addr*/NULL, printer->ipv6); cupsDNSSDDelete(printer->dnssd); free(printer->dnssd_name); free(printer->name); free(printer->icons[0]); free(printer->strings); free(printer->command); free(printer->device_uri); free(printer->directory); free(printer->hostname); ippDelete(printer->attrs); cupsArrayDelete(printer->jobs); free(printer); } // // 'dnssd_callback()' - Handle DNS-SD registration events. // static void dnssd_callback( cups_dnssd_service_t *service, // I - Service registration ippeve_printer_t *printer, // I - Printer cups_dnssd_flags_t flags) // I - Status flags { (void)service; if (flags & CUPS_DNSSD_FLAGS_COLLISION) { fputs("DNS-SD service name collision detected.\n", stderr); printer->dnssd_collision = 1; } } // // 'filter_cb()' - Filter printer attributes based on the requested array. // static int // O - 1 to copy, 0 to ignore filter_cb(ippeve_filter_t *filter, // I - Filter parameters ipp_t *dst, // I - Destination (unused) ipp_attribute_t *attr) // I - Source attribute { // Filter attributes as needed... #ifndef _WIN32 // Avoid MS compiler bug (void)dst; #endif // !_WIN32 ipp_tag_t group = ippGetGroupTag(attr); const char *name = ippGetName(attr); if ((filter->group_tag != IPP_TAG_ZERO && group != filter->group_tag && group != IPP_TAG_ZERO) || !name || (!strcmp(name, "media-col-database") && !cupsArrayFind(filter->ra, (void *)name))) return (0); return (!filter->ra || cupsArrayFind(filter->ra, (void *)name) != NULL); } // // 'find_job()' - Find a job specified in a request. // static ippeve_job_t * // O - Job or NULL find_job(ippeve_client_t *client) // I - Client { ipp_attribute_t *attr; // job-id or job-uri attribute ippeve_job_t key, // Job search key *job; // Matching job, if any if ((attr = ippFindAttribute(client->request, "job-uri", IPP_TAG_URI)) != NULL) { const char *uri = ippGetString(attr, 0, NULL); // URI value const char *uriptr = strrchr(uri, '/'); // Pointer to the last slash in the URI if (uriptr && _cups_isdigit(uriptr[1])) key.id = atoi(uriptr + 1); else return (NULL); } else if ((attr = ippFindAttribute(client->request, "job-id", IPP_TAG_INTEGER)) != NULL) key.id = ippGetInteger(attr, 0); cupsRWLockRead(&(client->printer->rwlock)); job = (ippeve_job_t *)cupsArrayFind(client->printer->jobs, &key); cupsRWUnlock(&(client->printer->rwlock)); return (job); } // // 'finish_document()' - Finish receiving a document file and start processing. // static void finish_document_data( ippeve_client_t *client, // I - Client ippeve_job_t *job) // I - Job { char filename[1024], // Filename buffer buffer[4096]; // Copy buffer ssize_t bytes; // Bytes read cups_array_t *ra; // Attributes to send in response cups_thread_t t; // Thread // Create a file for the request data... if ((job->fd = create_job_file(job, filename, sizeof(filename), client->printer->directory, NULL)) < 0) { respond_ipp(client, IPP_STATUS_ERROR_INTERNAL, "Unable to create print file: %s", strerror(errno)); goto abort_job; } if (Verbosity) fprintf(stderr, "Created job file \"%s\", format \"%s\".\n", filename, job->format); while ((bytes = httpRead(client->http, buffer, sizeof(buffer))) > 0) { if (write(job->fd, buffer, (size_t)bytes) < bytes) { respond_ipp(client, IPP_STATUS_ERROR_INTERNAL, "Unable to write print file: %s", strerror(errno)); goto cleanup_and_abort; } } if (bytes < 0) { // Got an error while reading the print data, so abort this job. respond_ipp(client, IPP_STATUS_ERROR_INTERNAL, "Unable to read print file."); goto cleanup_and_abort; } // Close the file descriptor if (close(job->fd)) { job->fd = -1; respond_ipp(client, IPP_STATUS_ERROR_INTERNAL, "Unable to close print file: %s", strerror(errno)); goto cleanup_and_abort; } job->fd = -1; job->filename = strdup(filename); job->state = IPP_JSTATE_PENDING; // Process the job... if ((t = cupsThreadCreate((cups_thread_func_t)process_job, job)) == CUPS_THREAD_INVALID) { respond_ipp(client, IPP_STATUS_ERROR_INTERNAL, "Unable to process job."); goto abort_job; } else { cupsThreadDetach(t); } // Return the job info... respond_ipp(client, IPP_STATUS_OK, NULL); ra = cupsArrayNew3((cups_array_cb_t)_cupsArrayStrcmp, NULL, NULL, 0, NULL, NULL); cupsArrayAdd(ra, "job-id"); cupsArrayAdd(ra, "job-state"); cupsArrayAdd(ra, "job-state-message"); cupsArrayAdd(ra, "job-state-reasons"); cupsArrayAdd(ra, "job-uri"); copy_job_attributes(client, job, ra); cupsArrayDelete(ra); return; // Cleanup file on error, then abort job... cleanup_and_abort: if (job->fd >= 0) { close(job->fd); job->fd = -1; } unlink(filename); // If we get here we had to abort the job... abort_job: job->state = IPP_JSTATE_ABORTED; job->completed = time(NULL); ra = cupsArrayNew3((cups_array_cb_t)_cupsArrayStrcmp, NULL, NULL, 0, NULL, NULL); cupsArrayAdd(ra, "job-id"); cupsArrayAdd(ra, "job-state"); cupsArrayAdd(ra, "job-state-reasons"); cupsArrayAdd(ra, "job-uri"); copy_job_attributes(client, job, ra); cupsArrayDelete(ra); } // // 'finish_uri()' - Finish fetching a document URI and start processing. // static void finish_document_uri( ippeve_client_t *client, // I - Client ippeve_job_t *job) // I - Job { ipp_attribute_t *uri; // document-uri char scheme[256], // URI scheme userpass[256], // Username and password info hostname[256], // Hostname resource[1024]; // Resource path int port; // Port number http_uri_status_t uri_status; // URI decode status http_encryption_t encryption; // Encryption to use, if any http_t *http; // Connection for http/https URIs http_status_t status; // Access status for http/https URIs int infile; // Input file for local file URIs char filename[1024], // Filename buffer buffer[4096]; // Copy buffer ssize_t bytes; // Bytes read ipp_attribute_t *attr; // Current attribute cups_array_t *ra; // Attributes to send in response // Do we have a file to print? if (have_document_data(client)) { flush_document_data(client); respond_ipp(client, IPP_STATUS_ERROR_BAD_REQUEST, "Unexpected document data following request."); goto abort_job; } // Do we have a document URI? if ((uri = ippFindAttribute(client->request, "document-uri", IPP_TAG_URI)) == NULL) { respond_ipp(client, IPP_STATUS_ERROR_BAD_REQUEST, "Missing document-uri."); goto abort_job; } if (ippGetCount(uri) != 1) { respond_ipp(client, IPP_STATUS_ERROR_BAD_REQUEST, "Too many document-uri values."); goto abort_job; } if ((uri_status = httpSeparateURI(HTTP_URI_CODING_ALL, ippGetString(uri, 0, NULL), scheme, sizeof(scheme), userpass, sizeof(userpass), hostname, sizeof(hostname), &port, resource, sizeof(resource))) < HTTP_URI_STATUS_OK) { respond_ipp(client, IPP_STATUS_ERROR_BAD_REQUEST, "Bad document-uri: %s", httpURIStatusString(uri_status)); goto abort_job; } if (strcmp(scheme, "file") && strcmp(scheme, "https") && strcmp(scheme, "http")) { respond_ipp(client, IPP_STATUS_ERROR_URI_SCHEME, "URI scheme '%s' not supported.", scheme); goto abort_job; } // Get the document format for the job... cupsRWLockWrite(&(client->printer->rwlock)); if ((attr = ippFindAttribute(job->attrs, "document-format", IPP_TAG_MIMETYPE)) != NULL) job->format = ippGetString(attr, 0, NULL); else job->format = "application/octet-stream"; // Create a file for the request data... if ((job->fd = create_job_file(job, filename, sizeof(filename), client->printer->directory, NULL)) < 0) { cupsRWUnlock(&(client->printer->rwlock)); respond_ipp(client, IPP_STATUS_ERROR_INTERNAL, "Unable to create print file: %s", strerror(errno)); goto abort_job; } cupsRWUnlock(&(client->printer->rwlock)); if (!strcmp(scheme, "file")) { if ((infile = open(resource, O_RDONLY | O_BINARY)) < 0) { respond_ipp(client, IPP_STATUS_ERROR_DOCUMENT_ACCESS, "Unable to access URI '%s': %s", ippGetString(uri, 0, NULL), strerror(errno)); goto abort_job; } do { if ((bytes = read(infile, buffer, sizeof(buffer))) < 0 && (errno == EAGAIN || errno == EINTR)) { bytes = 1; } else if (bytes > 0 && write(job->fd, buffer, (size_t)bytes) < bytes) { respond_ipp(client, IPP_STATUS_ERROR_INTERNAL, "Unable to write print file: %s", strerror(errno)); close(infile); goto cleanup_and_abort; } } while (bytes > 0); close(infile); } else { if (port == 443 || !strcmp(scheme, "https")) encryption = HTTP_ENCRYPTION_ALWAYS; else encryption = HTTP_ENCRYPTION_IF_REQUESTED; if ((http = httpConnect2(hostname, port, NULL, AF_UNSPEC, encryption, 1, 30000, NULL)) == NULL) { respond_ipp(client, IPP_STATUS_ERROR_DOCUMENT_ACCESS, "Unable to connect to '%s' on port %d: %s", hostname, port, cupsGetErrorString()); goto cleanup_and_abort; } httpClearFields(http); httpSetField(http, HTTP_FIELD_ACCEPT_LANGUAGE, "en"); if (!httpWriteRequest(http, "GET", resource)) { respond_ipp(client, IPP_STATUS_ERROR_DOCUMENT_ACCESS, "Unable to GET URI: %s", strerror(errno)); httpClose(http); goto cleanup_and_abort; } while ((status = httpUpdate(http)) == HTTP_STATUS_CONTINUE); if (status != HTTP_STATUS_OK) { respond_ipp(client, IPP_STATUS_ERROR_DOCUMENT_ACCESS, "Unable to GET URI: %s", httpStatusString(status)); httpClose(http); goto cleanup_and_abort; } while ((bytes = httpRead(http, buffer, sizeof(buffer))) > 0) { if (write(job->fd, buffer, (size_t)bytes) < bytes) { respond_ipp(client, IPP_STATUS_ERROR_INTERNAL, "Unable to write print file: %s", strerror(errno)); httpClose(http); goto cleanup_and_abort; } } httpClose(http); } if (close(job->fd)) { job->fd = -1; respond_ipp(client, IPP_STATUS_ERROR_INTERNAL, "Unable to close print file: %s", strerror(errno)); goto cleanup_and_abort; } cupsRWLockWrite(&(client->printer->rwlock)); job->fd = -1; job->filename = strdup(filename); job->state = IPP_JSTATE_PENDING; cupsRWUnlock(&(client->printer->rwlock)); // Process the job... process_job(job); // Return the job info... respond_ipp(client, IPP_STATUS_OK, NULL); ra = cupsArrayNew3((cups_array_cb_t)_cupsArrayStrcmp, NULL, NULL, 0, NULL, NULL); cupsArrayAdd(ra, "job-id"); cupsArrayAdd(ra, "job-state"); cupsArrayAdd(ra, "job-state-reasons"); cupsArrayAdd(ra, "job-uri"); copy_job_attributes(client, job, ra); cupsArrayDelete(ra); return; // Cleanup and then abort... cleanup_and_abort: if (job->fd >= 0) { close(job->fd); job->fd = -1; } unlink(filename); // If we get here we had to abort the job... abort_job: job->state = IPP_JSTATE_ABORTED; job->completed = time(NULL); ra = cupsArrayNew3((cups_array_cb_t)_cupsArrayStrcmp, NULL, NULL, 0, NULL, NULL); cupsArrayAdd(ra, "job-id"); cupsArrayAdd(ra, "job-state"); cupsArrayAdd(ra, "job-state-reasons"); cupsArrayAdd(ra, "job-uri"); copy_job_attributes(client, job, ra); cupsArrayDelete(ra); } // // 'flush_document_data()' - Safely flush remaining document data. // static void flush_document_data( ippeve_client_t *client) // I - Client { char buffer[8192]; // Read buffer if (httpGetState(client->http) == HTTP_STATE_POST_RECV) { while (httpRead(client->http, buffer, sizeof(buffer)) > 0); } } // // 'have_document_data()' - Determine whether we have more document data. // static bool // O - `true` if data is present, `false` otherwise have_document_data( ippeve_client_t *client) // I - Client { char temp; // Data if (httpGetState(client->http) != HTTP_STATE_POST_RECV) return (false); else return (httpPeek(client->http, &temp, 1) > 0); } // // 'html_escape()' - Write a HTML-safe string. // static bool // O - `true` on success, `false` on error html_escape(ippeve_client_t *client, // I - Client const char *s, // I - String to write size_t slen) // I - Number of characters to write { const char *start, // Start of segment *end; // End of string start = s; end = s + (slen > 0 ? slen : strlen(s)); while (*s && s < end) { if (*s == '&' || *s == '<') { if (s > start) { if (httpWrite(client->http, start, (size_t)(s - start)) < 0) return (false); } if (*s == '&') { if (httpWrite(client->http, "&", 5) < 0) return (false); } else if (httpWrite(client->http, "<", 4) < 0) return (false); start = s + 1; } s ++; } if (s > start) { if (httpWrite(client->http, start, (size_t)(s - start)) < 0) return (false); } return (true); } // // 'html_footer()' - Show the web interface footer. // // This function also writes the trailing 0-length chunk. // static bool // O - `true` on success, `false` on error html_footer(ippeve_client_t *client) // I - Client { if (!html_printf(client, "\n" "\n" "\n")) return (false); return (httpWrite(client->http, "", 0) >= 0); } // // 'html_header()' - Show the web interface header and title. // static bool // O - `true` on success, `false` on error html_header(ippeve_client_t *client, // I - Client const char *title, // I - Title int refresh) // I - Refresh timer, if any { bool ret = true; // Return value ret &= html_printf(client, "\n" "\n" "\n" "%s\n" "\n" "\n" "\n", title); if (refresh > 0) ret &= html_printf(client, "\n", refresh); ret &= html_printf(client, "\n" "\n" "\n" "\n" "" "" "" "" "
StatusSuppliesMedia
\n" "
\n", !strcmp(client->uri, "/") ? " sel" : "", !strcmp(client->uri, "/supplies") ? " sel" : "", !strcmp(client->uri, "/media") ? " sel" : ""); return (ret); } // // 'html_printf()' - Send formatted text to the client, quoting as needed. // static bool // O - `true` on success, `false` on error html_printf(ippeve_client_t *client, // I - Client const char *format, // I - Printf-style format string ...) // I - Additional arguments as needed { bool ret = false; // Return value va_list ap; // Pointer to arguments const char *start; // Start of string char size, // Size character (h, l, L) type; // Format type character int width, // Width of field prec; // Number of characters of precision char tformat[100], // Temporary format string for sprintf() *tptr, // Pointer into temporary format temp[1024]; // Buffer for formatted numbers char *s; // Pointer to string // Loop through the format string, formatting as needed... va_start(ap, format); start = format; while (*format) { if (*format == '%') { if (format > start) { if (httpWrite(client->http, start, (size_t)(format - start)) < 0) goto error; } tptr = tformat; *tptr++ = *format++; if (*format == '%') { if (httpWrite(client->http, "%", 1) < 0) goto error; format ++; start = format; continue; } else if (strchr(" -+#\'", *format)) *tptr++ = *format++; if (*format == '*') { // Get width from argument... format ++; width = va_arg(ap, int); snprintf(tptr, sizeof(tformat) - (size_t)(tptr - tformat), "%d", width); tptr += strlen(tptr); } else { width = 0; while (_cups_isdigit(*format)) { if (tptr < (tformat + sizeof(tformat) - 1)) *tptr++ = *format; width = width * 10 + *format++ - '0'; } } if (*format == '.') { if (tptr < (tformat + sizeof(tformat) - 1)) *tptr++ = *format; format ++; if (*format == '*') { // Get precision from argument... format ++; prec = va_arg(ap, int); snprintf(tptr, sizeof(tformat) - (size_t)(tptr - tformat), "%d", prec); tptr += strlen(tptr); } else { prec = 0; while (_cups_isdigit(*format)) { if (tptr < (tformat + sizeof(tformat) - 1)) *tptr++ = *format; prec = prec * 10 + *format++ - '0'; } } } if (*format == 'l' && format[1] == 'l') { size = 'L'; if (tptr < (tformat + sizeof(tformat) - 2)) { *tptr++ = 'l'; *tptr++ = 'l'; } format += 2; } else if (*format == 'h' || *format == 'l' || *format == 'L') { if (tptr < (tformat + sizeof(tformat) - 1)) *tptr++ = *format; size = *format++; } else size = 0; if (!*format) { start = format; break; } if (tptr < (tformat + sizeof(tformat) - 1)) *tptr++ = *format; type = *format++; *tptr = '\0'; start = format; switch (type) { case 'E' : // Floating point formats case 'G' : case 'e' : case 'f' : case 'g' : if ((size_t)(width + 2) > sizeof(temp)) break; snprintf(temp, sizeof(temp), tformat, va_arg(ap, double)); if (httpWrite(client->http, temp, strlen(temp)) < 0) goto error; break; case 'B' : // Integer formats case 'X' : case 'b' : case 'd' : case 'i' : case 'o' : case 'u' : case 'x' : if ((size_t)(width + 2) > sizeof(temp)) break; # ifdef HAVE_LONG_LONG if (size == 'L') snprintf(temp, sizeof(temp), tformat, va_arg(ap, long long)); else # endif // HAVE_LONG_LONG if (size == 'l') snprintf(temp, sizeof(temp), tformat, va_arg(ap, long)); else snprintf(temp, sizeof(temp), tformat, va_arg(ap, int)); if (httpWrite(client->http, temp, strlen(temp)) < 0) goto error; break; case 'p' : // Pointer value if ((size_t)(width + 2) > sizeof(temp)) break; snprintf(temp, sizeof(temp), tformat, va_arg(ap, void *)); if (httpWrite(client->http, temp, strlen(temp)) < 0) goto error; break; case 'c' : // Character or character array if (width <= 1) { temp[0] = (char)va_arg(ap, int); temp[1] = '\0'; html_escape(client, temp, 1); } else html_escape(client, va_arg(ap, char *), (size_t)width); break; case 's' : // String if ((s = va_arg(ap, char *)) == NULL) s = "(null)"; html_escape(client, s, strlen(s)); break; } } else format ++; } if (format > start) { if (httpWrite(client->http, start, (size_t)(format - start)) < 0) goto error; } ret = true; error: va_end(ap); return (ret); } // // 'ipp_cancel_job()' - Cancel a job. // static void ipp_cancel_job(ippeve_client_t *client) // I - Client { ippeve_job_t *job; // Job information // Get the job... if ((job = find_job(client)) == NULL) { respond_ipp(client, IPP_STATUS_ERROR_NOT_FOUND, "Job does not exist."); return; } // See if the job is already completed, canceled, or aborted; if so, we can't cancel... switch (job->state) { case IPP_JSTATE_CANCELED : respond_ipp(client, IPP_STATUS_ERROR_NOT_POSSIBLE, "Job #%d is already canceled - can\'t cancel.", job->id); break; case IPP_JSTATE_ABORTED : respond_ipp(client, IPP_STATUS_ERROR_NOT_POSSIBLE, "Job #%d is already aborted - can\'t cancel.", job->id); break; case IPP_JSTATE_COMPLETED : respond_ipp(client, IPP_STATUS_ERROR_NOT_POSSIBLE, "Job #%d is already completed - can\'t cancel.", job->id); break; default : // Cancel the job... cupsRWLockWrite(&(client->printer->rwlock)); if (job->state == IPP_JSTATE_PROCESSING || (job->state == IPP_JSTATE_HELD && job->fd >= 0)) { job->cancel = 1; } else { client->printer->active_job = NULL; job->state = IPP_JSTATE_CANCELED; job->completed = time(NULL); } cupsRWUnlock(&(client->printer->rwlock)); respond_ipp(client, IPP_STATUS_OK, NULL); break; } } // // 'ipp_cancel_jobs()' - Cancel all jobs. // // Note: Since ippeveprinter doesn't do spooling, this really just cancels the // current job. // static void ipp_cancel_jobs( ippeve_client_t *client) // I - Client { ippeve_job_t *job; // Job information cupsRWLockWrite(&client->printer->rwlock); if ((job = client->printer->active_job) != NULL) { // See if the job is already completed, canceled, or aborted; if so, we can't cancel... if (job->state < IPP_JSTATE_CANCELED) { // Cancel the job... if (job->state == IPP_JSTATE_PROCESSING || (job->state == IPP_JSTATE_HELD && job->fd >= 0)) { job->cancel = 1; } else { client->printer->active_job = NULL; job->state = IPP_JSTATE_CANCELED; job->completed = time(NULL); } } } respond_ipp(client, IPP_STATUS_OK, NULL); cupsRWUnlock(&client->printer->rwlock); } // // 'ipp_close_job()' - Close an open job. // static void ipp_close_job(ippeve_client_t *client) // I - Client { ippeve_job_t *job; // Job information // Get the job... if ((job = find_job(client)) == NULL) { respond_ipp(client, IPP_STATUS_ERROR_NOT_FOUND, "Job does not exist."); return; } // See if the job is already completed, canceled, or aborted; if so, we can't cancel... switch (job->state) { case IPP_JSTATE_CANCELED : respond_ipp(client, IPP_STATUS_ERROR_NOT_POSSIBLE, "Job #%d is canceled - can\'t close.", job->id); break; case IPP_JSTATE_ABORTED : respond_ipp(client, IPP_STATUS_ERROR_NOT_POSSIBLE, "Job #%d is aborted - can\'t close.", job->id); break; case IPP_JSTATE_COMPLETED : respond_ipp(client, IPP_STATUS_ERROR_NOT_POSSIBLE, "Job #%d is completed - can\'t close.", job->id); break; case IPP_JSTATE_PROCESSING : case IPP_JSTATE_STOPPED : respond_ipp(client, IPP_STATUS_ERROR_NOT_POSSIBLE, "Job #%d is already closed.", job->id); break; default : respond_ipp(client, IPP_STATUS_OK, NULL); break; } } // // 'ipp_create_job()' - Create a job object. // static void ipp_create_job(ippeve_client_t *client) // I - Client { ippeve_job_t *job; // New job cups_array_t *ra; // Attributes to send in response // Do we have a file to print? if (have_document_data(client)) { flush_document_data(client); respond_ipp(client, IPP_STATUS_ERROR_BAD_REQUEST, "Unexpected document data following request."); return; } // Validate print job attributes... if (!valid_job_attributes(client)) return; // Create the job... if ((job = create_job(client)) == NULL) { respond_ipp(client, IPP_STATUS_ERROR_BUSY, "Currently printing another job."); return; } // Return the job info... respond_ipp(client, IPP_STATUS_OK, NULL); ra = cupsArrayNew3((cups_array_cb_t)_cupsArrayStrcmp, NULL, NULL, 0, NULL, NULL); cupsArrayAdd(ra, "job-id"); cupsArrayAdd(ra, "job-state"); cupsArrayAdd(ra, "job-state-message"); cupsArrayAdd(ra, "job-state-reasons"); cupsArrayAdd(ra, "job-uri"); copy_job_attributes(client, job, ra); cupsArrayDelete(ra); } // // 'ipp_get_job_attributes()' - Get the attributes for a job object. // static void ipp_get_job_attributes( ippeve_client_t *client) // I - Client { ippeve_job_t *job; // Job cups_array_t *ra; // requested-attributes if ((job = find_job(client)) == NULL) { respond_ipp(client, IPP_STATUS_ERROR_NOT_FOUND, "Job not found."); return; } respond_ipp(client, IPP_STATUS_OK, NULL); ra = ippCreateRequestedArray(client->request); copy_job_attributes(client, job, ra); cupsArrayDelete(ra); } // // 'ipp_get_jobs()' - Get a list of job objects. // static void ipp_get_jobs(ippeve_client_t *client) // I - Client { ipp_attribute_t *attr; // Current attribute const char *which_jobs = NULL; // which-jobs values int job_comparison; // Job comparison ipp_jstate_t job_state; // job-state value int first_index, // First job index limit, // Maximum number of jobs to return matches; // Number of jobs that match int i, // Current job index first_i, // First job index (0-based) count; // Number of jobs const char *username; // Username ippeve_job_t *job; // Current job pointer cups_array_t *ra; // Requested attributes array // See if the "which-jobs" attribute have been specified... if ((attr = ippFindAttribute(client->request, "which-jobs", IPP_TAG_KEYWORD)) != NULL) { which_jobs = ippGetString(attr, 0, NULL); fprintf(stderr, "%s Get-Jobs which-jobs=%s", client->hostname, which_jobs); } if (!which_jobs || !strcmp(which_jobs, "not-completed")) { job_comparison = -1; job_state = IPP_JSTATE_STOPPED; } else if (!strcmp(which_jobs, "completed")) { job_comparison = 1; job_state = IPP_JSTATE_CANCELED; } else if (!strcmp(which_jobs, "aborted")) { job_comparison = 0; job_state = IPP_JSTATE_ABORTED; } else if (!strcmp(which_jobs, "all")) { job_comparison = 1; job_state = IPP_JSTATE_PENDING; } else if (!strcmp(which_jobs, "canceled")) { job_comparison = 0; job_state = IPP_JSTATE_CANCELED; } else if (!strcmp(which_jobs, "pending")) { job_comparison = 0; job_state = IPP_JSTATE_PENDING; } else if (!strcmp(which_jobs, "pending-held")) { job_comparison = 0; job_state = IPP_JSTATE_HELD; } else if (!strcmp(which_jobs, "processing")) { job_comparison = 0; job_state = IPP_JSTATE_PROCESSING; } else if (!strcmp(which_jobs, "processing-stopped")) { job_comparison = 0; job_state = IPP_JSTATE_STOPPED; } else { respond_ipp(client, IPP_STATUS_ERROR_ATTRIBUTES_OR_VALUES, "The which-jobs value \"%s\" is not supported.", which_jobs); ippAddString(client->response, IPP_TAG_UNSUPPORTED_GROUP, IPP_TAG_KEYWORD, "which-jobs", NULL, which_jobs); return; } // See if they want to limit the number of jobs reported... if ((attr = ippFindAttribute(client->request, "limit", IPP_TAG_INTEGER)) != NULL) { limit = ippGetInteger(attr, 0); fprintf(stderr, "%s Get-Jobs limit=%d", client->hostname, limit); } else { limit = 0; } if ((attr = ippFindAttribute(client->request, "first-index", IPP_TAG_INTEGER)) != NULL && (first_index = ippGetInteger(attr, 0)) >= 1) { first_i = (size_t)first_index - 1; fprintf(stderr, "%s Get-Jobs first-index=%d", client->hostname, first_index); } else { first_i = 0; } // See if we only want to see jobs for a specific user... username = NULL; if ((attr = ippFindAttribute(client->request, "my-jobs", IPP_TAG_BOOLEAN)) != NULL) { int my_jobs = ippGetBoolean(attr, 0); fprintf(stderr, "%s Get-Jobs my-jobs=%s\n", client->hostname, my_jobs ? "true" : "false"); if (my_jobs) { if ((attr = ippFindAttribute(client->request, "requesting-user-name", IPP_TAG_NAME)) == NULL) { respond_ipp(client, IPP_STATUS_ERROR_BAD_REQUEST, "Need requesting-user-name with my-jobs."); return; } username = ippGetString(attr, 0, NULL); fprintf(stderr, "%s Get-Jobs requesting-user-name=\"%s\"\n", client->hostname, username); } } // OK, build a list of jobs for this printer... ra = ippCreateRequestedArray(client->request); respond_ipp(client, IPP_STATUS_OK, NULL); cupsRWLockRead(&(client->printer->rwlock)); for (i = 0, count = cupsArrayGetCount(client->printer->jobs), matches = 0; i < count && (limit <= 0 || matches < limit); i ++) { // Filter out jobs that don't match... job = (ippeve_job_t *)cupsArrayGetElement(client->printer->jobs, i); if ((job_comparison < 0 && job->state > job_state) || (job_comparison == 0 && job->state != job_state) || (job_comparison > 0 && job->state < job_state) || i < first_i || (username && job->username && strcasecmp(username, job->username))) continue; if (matches > 0) ippAddSeparator(client->response); matches ++; copy_job_attributes(client, job, ra); } cupsArrayDelete(ra); cupsRWUnlock(&(client->printer->rwlock)); } // // 'ipp_get_printer_attributes()' - Get the attributes for a printer object. // static void ipp_get_printer_attributes( ippeve_client_t *client) // I - Client { cups_array_t *ra; // Requested attributes array ippeve_printer_t *printer; // Printer // Send the attributes... ra = ippCreateRequestedArray(client->request); printer = client->printer; respond_ipp(client, IPP_STATUS_OK, NULL); cupsRWLockRead(&(printer->rwlock)); copy_attributes(client->response, printer->attrs, ra, IPP_TAG_ZERO, IPP_TAG_CUPS_CONST); if (!ra || cupsArrayFind(ra, "printer-config-change-date-time")) ippAddDate(client->response, IPP_TAG_PRINTER, "printer-config-change-date-time", ippTimeToDate(printer->config_time)); if (!ra || cupsArrayFind(ra, "printer-config-change-time")) ippAddInteger(client->response, IPP_TAG_PRINTER, IPP_TAG_INTEGER, "printer-config-change-time", (int)(printer->config_time - printer->start_time)); if (!ra || cupsArrayFind(ra, "printer-current-time")) ippAddDate(client->response, IPP_TAG_PRINTER, "printer-current-time", ippTimeToDate(time(NULL))); if (!ra || cupsArrayFind(ra, "printer-dns-sd-name")) ippAddString(client->response, IPP_TAG_PRINTER, IPP_TAG_NAME, "printer-dns-sd-name", /*language*/NULL, printer->dnssd_name); if (!ra || cupsArrayFind(ra, "printer-icons")) { char uris[3][1024]; // Buffers for URIs const char *values[3]; // Values for attribute httpAssembleURI(HTTP_URI_CODING_ALL, uris[0], sizeof(uris[0]), WEB_SCHEME, NULL, client->host_field, client->host_port, "/icon-sm.png"); httpAssembleURI(HTTP_URI_CODING_ALL, uris[1], sizeof(uris[1]), WEB_SCHEME, NULL, client->host_field, client->host_port, "/icon.png"); httpAssembleURI(HTTP_URI_CODING_ALL, uris[2], sizeof(uris[2]), WEB_SCHEME, NULL, client->host_field, client->host_port, "/icon-lg.png"); values[0] = uris[0]; values[1] = uris[1]; values[2] = uris[2]; ippAddStrings(client->response, IPP_TAG_PRINTER, IPP_TAG_URI, "printer-icons", 3, NULL, values); } if (!ra || cupsArrayFind(ra, "printer-more-info")) { char uri[1024]; // URI value httpAssembleURI(HTTP_URI_CODING_ALL, uri, sizeof(uri), WEB_SCHEME, NULL, client->host_field, client->host_port, "/"); ippAddString(client->response, IPP_TAG_PRINTER, IPP_TAG_URI, "printer-more-info", NULL, uri); } if (!ra || cupsArrayFind(ra, "printer-state")) ippAddInteger(client->response, IPP_TAG_PRINTER, IPP_TAG_ENUM, "printer-state", (int)printer->state); if (!ra || cupsArrayFind(ra, "printer-state-change-date-time")) ippAddDate(client->response, IPP_TAG_PRINTER, "printer-state-change-date-time", ippTimeToDate(printer->state_time)); if (!ra || cupsArrayFind(ra, "printer-state-change-time")) ippAddInteger(client->response, IPP_TAG_PRINTER, IPP_TAG_INTEGER, "printer-state-change-time", (int)(printer->state_time - printer->start_time)); if (!ra || cupsArrayFind(ra, "printer-state-message")) { static const char * const messages[] = { "Idle.", "Printing.", "Stopped." }; ippAddString(client->response, IPP_TAG_PRINTER, IPP_CONST_TAG(IPP_TAG_TEXT), "printer-state-message", NULL, messages[printer->state - IPP_PSTATE_IDLE]); } if (!ra || cupsArrayFind(ra, "printer-state-reasons")) { if (printer->state_reasons == IPPEVE_PREASON_NONE) { ippAddString(client->response, IPP_TAG_PRINTER, IPP_CONST_TAG(IPP_TAG_KEYWORD), "printer-state-reasons", NULL, "none"); } else { ipp_attribute_t *attr = NULL; // printer-state-reasons ippeve_preason_t bit; // Reason bit int i; // Looping var char reason[32]; // Reason string for (i = 0, bit = 1; i < (int)(sizeof(ippeve_preason_strings) / sizeof(ippeve_preason_strings[0])); i ++, bit *= 2) { if (printer->state_reasons & bit) { snprintf(reason, sizeof(reason), "%s-%s", ippeve_preason_strings[i], printer->state == IPP_PSTATE_IDLE ? "report" : printer->state == IPP_PSTATE_PROCESSING ? "warning" : "error"); if (attr) ippSetString(client->response, &attr, ippGetCount(attr), reason); else attr = ippAddString(client->response, IPP_TAG_PRINTER, IPP_TAG_KEYWORD, "printer-state-reasons", NULL, reason); } } } } if (!ra || cupsArrayFind(ra, "printer-strings-uri")) { char uri[1024]; // URI value httpAssembleURI(HTTP_URI_CODING_ALL, uri, sizeof(uri), WEB_SCHEME, NULL, client->host_field, client->host_port, "/en.strings"); ippAddString(client->response, IPP_TAG_PRINTER, IPP_TAG_URI, "printer-strings-uri", NULL, uri); } if (!ra || cupsArrayFind(ra, "printer-supply-info-uri")) { char uri[1024]; // URI value httpAssembleURI(HTTP_URI_CODING_ALL, uri, sizeof(uri), WEB_SCHEME, NULL, client->host_field, client->host_port, "/supplies"); ippAddString(client->response, IPP_TAG_PRINTER, IPP_TAG_URI, "printer-supply-info-uri", NULL, uri); } if (!ra || cupsArrayFind(ra, "printer-up-time")) ippAddInteger(client->response, IPP_TAG_PRINTER, IPP_TAG_INTEGER, "printer-up-time", (int)(time(NULL) - printer->start_time)); if (!ra || cupsArrayFind(ra, "printer-uri-supported")) { char uris[2][1024]; // Buffers for URIs const char *values[2]; // Values for attribute size_t num_values = 0; // Number of values httpAssembleURI(HTTP_URI_CODING_ALL, uris[0], sizeof(uris[0]), "ipp", NULL, client->host_field, client->host_port, "/ipp/print"); values[num_values ++] = uris[0]; httpAssembleURI(HTTP_URI_CODING_ALL, uris[1], sizeof(uris[1]), "ipps", NULL, client->host_field, client->host_port, "/ipp/print"); values[num_values ++] = uris[1]; ippAddStrings(client->response, IPP_TAG_PRINTER, IPP_TAG_URI, "printer-uri-supported", num_values, NULL, values); } if (!ra || cupsArrayFind(ra, "queued-job-count")) ippAddInteger(client->response, IPP_TAG_PRINTER, IPP_TAG_INTEGER, "queued-job-count", printer->active_job && printer->active_job->state < IPP_JSTATE_CANCELED); cupsRWUnlock(&(printer->rwlock)); cupsArrayDelete(ra); } // // 'ipp_identify_printer()' - Beep or display a message. // static void ipp_identify_printer( ippeve_client_t *client) // I - Client { ipp_attribute_t *actions, // identify-actions *message; // message actions = ippFindAttribute(client->request, "identify-actions", IPP_TAG_KEYWORD); message = ippFindAttribute(client->request, "message", IPP_TAG_TEXT); if (!actions || ippContainsString(actions, "sound")) { #ifdef __APPLE__ pid_t pid; // Process ID for "afplay" utility static const char * const afplay[3] = { // Arguments for "afplay" utility "/usr/bin/afplay", "/System/Library/Sounds/Ping.aiff", NULL }; posix_spawn(&pid, afplay[0], NULL, NULL, (char **)afplay, NULL); #else putchar(0x07); fflush(stdout); #endif // __APPLE__ } if (ippContainsString(actions, "display")) printf("IDENTIFY from %s: %s\n", client->hostname, message ? ippGetString(message, 0, NULL) : "No message supplied"); respond_ipp(client, IPP_STATUS_OK, NULL); } // // 'ipp_print_job()' - Create a job object with an attached document. // static void ipp_print_job(ippeve_client_t *client) // I - Client { ippeve_job_t *job; // New job // Validate print job attributes... if (!valid_job_attributes(client)) { flush_document_data(client); return; } // Do we have a file to print? if (!have_document_data(client)) { respond_ipp(client, IPP_STATUS_ERROR_BAD_REQUEST, "No file in request."); return; } // Create the job... if ((job = create_job(client)) == NULL) { respond_ipp(client, IPP_STATUS_ERROR_BUSY, "Currently printing another job."); return; } // Then finish getting the document data and process things... finish_document_data(client, job); } // // 'ipp_print_uri()' - Create a job object with a referenced document. // static void ipp_print_uri(ippeve_client_t *client) // I - Client { ippeve_job_t *job; // New job // Validate print job attributes... if (!valid_job_attributes(client)) { flush_document_data(client); return; } // Create the job... if ((job = create_job(client)) == NULL) { respond_ipp(client, IPP_STATUS_ERROR_BUSY, "Currently printing another job."); return; } // Then finish getting the document data and process things... finish_document_uri(client, job); } // // 'ipp_send_document()' - Add an attached document to a job object created with // Create-Job. // static void ipp_send_document( ippeve_client_t *client) // I - Client { ippeve_job_t *job; // Job information ipp_attribute_t *attr; // Current attribute int have_data; // Have document data? // Get the job... if ((job = find_job(client)) == NULL) { flush_document_data(client); respond_ipp(client, IPP_STATUS_ERROR_NOT_FOUND, "Job does not exist."); return; } // See if we already have a document for this job or the job has already in a terminating state... have_data = have_document_data(client); if ((job->filename || job->fd >= 0) && have_data) { flush_document_data(client); respond_ipp(client, IPP_STATUS_ERROR_MULTIPLE_JOBS_NOT_SUPPORTED, "Multiple document jobs are not supported."); return; } else if (job->state > IPP_JSTATE_HELD && have_data) { flush_document_data(client); respond_ipp(client, IPP_STATUS_ERROR_NOT_POSSIBLE, "Job is not in a pending state."); return; } // Make sure we have the "last-document" operation attribute... if ((attr = ippFindAttribute(client->request, "last-document", IPP_TAG_ZERO)) == NULL) { flush_document_data(client); respond_ipp(client, IPP_STATUS_ERROR_BAD_REQUEST, "Missing required last-document attribute."); return; } else if (ippGetGroupTag(attr) != IPP_TAG_OPERATION) { flush_document_data(client); respond_ipp(client, IPP_STATUS_ERROR_BAD_REQUEST, "The last-document attribute is not in the operation group."); return; } else if (ippGetValueTag(attr) != IPP_TAG_BOOLEAN || ippGetCount(attr) != 1) { flush_document_data(client); respond_unsupported(client, attr); return; } // Validate document attributes... if (have_data && !valid_doc_attributes(client)) { flush_document_data(client); return; } if (!have_data && !job->filename) job->state = IPP_JSTATE_ABORTED; // Then finish getting the document data and process things... cupsRWLockWrite(&(client->printer->rwlock)); copy_attributes(job->attrs, client->request, NULL, IPP_TAG_JOB, 0); if ((attr = ippFindAttribute(job->attrs, "document-format-detected", IPP_TAG_MIMETYPE)) != NULL) job->format = ippGetString(attr, 0, NULL); else if ((attr = ippFindAttribute(job->attrs, "document-format-supplied", IPP_TAG_MIMETYPE)) != NULL) job->format = ippGetString(attr, 0, NULL); else job->format = "application/octet-stream"; cupsRWUnlock(&(client->printer->rwlock)); if (have_data) finish_document_data(client, job); } // // 'ipp_send_uri()' - Add a referenced document to a job object created with // Create-Job. // static void ipp_send_uri(ippeve_client_t *client) // I - Client { ippeve_job_t *job; // Job information ipp_attribute_t *attr; // Current attribute // Get the job... if ((job = find_job(client)) == NULL) { respond_ipp(client, IPP_STATUS_ERROR_NOT_FOUND, "Job does not exist."); return; } // See if we already have a document for this job or the job has already in a non-terminating state... if (job->filename || job->fd >= 0) { respond_ipp(client, IPP_STATUS_ERROR_MULTIPLE_JOBS_NOT_SUPPORTED, "Multiple document jobs are not supported."); return; } else if (job->state > IPP_JSTATE_HELD) { flush_document_data(client); respond_ipp(client, IPP_STATUS_ERROR_NOT_POSSIBLE, "Job is not in a pending state."); return; } if ((attr = ippFindAttribute(client->request, "last-document", IPP_TAG_ZERO)) == NULL) { respond_ipp(client, IPP_STATUS_ERROR_BAD_REQUEST, "Missing required last-document attribute."); return; } else if (ippGetGroupTag(attr) != IPP_TAG_OPERATION) { respond_ipp(client, IPP_STATUS_ERROR_BAD_REQUEST, "The last-document attribute is not in the operation group."); return; } else if (ippGetValueTag(attr) != IPP_TAG_BOOLEAN || ippGetCount(attr) != 1) { respond_unsupported(client, attr); return; } // Validate document attributes... if (!valid_doc_attributes(client)) { flush_document_data(client); return; } // Then finish getting the document data and process things... cupsRWLockWrite(&(client->printer->rwlock)); copy_attributes(job->attrs, client->request, NULL, IPP_TAG_JOB, 0); if ((attr = ippFindAttribute(job->attrs, "document-format-detected", IPP_TAG_MIMETYPE)) != NULL) job->format = ippGetString(attr, 0, NULL); else if ((attr = ippFindAttribute(job->attrs, "document-format-supplied", IPP_TAG_MIMETYPE)) != NULL) job->format = ippGetString(attr, 0, NULL); else job->format = "application/octet-stream"; cupsRWUnlock(&(client->printer->rwlock)); finish_document_uri(client, job); } // // 'ipp_validate_job()' - Validate job creation attributes. // static void ipp_validate_job(ippeve_client_t *client) // I - Client { if (valid_job_attributes(client)) respond_ipp(client, IPP_STATUS_OK, NULL); } // // 'ippfile_attr_cb()' - Determine whether an attribute should be loaded. // static bool // O - `true` to use, `false` to ignore ippfile_attr_cb( ipp_file_t *f, // I - IPP file void *user_data, // I - User data pointer (unused) const char *attr) // I - Attribute name { size_t i; // Current element static const char * const ignored[] = { // Ignored attributes "attributes-charset", "attributes-natural-language", "charset-configured", "charset-supported", "device-service-count", "device-uuid", "document-format-varying-attributes", "generated-natural-language-supported", "identify-actions-default", "identify-actions-supported", "ipp-features-supported", "ipp-versions-supported", "ippget-event-life", "job-creation-attributes-supported", "job-hold-until-supported", "job-hold-until-time-supported", "job-ids-supported", "job-k-octets-supported", "job-priority-default", "job-priority-supported", "job-settable-attributes-supported", "job-sheets-default", "job-sheets-supported", "media-col-supported", "multiple-document-handling-supported", "multiple-document-jobs-supported", "multiple-operation-time-out", "multiple-operation-time-out-action", "natural-language-configured", "notify-attributes-supported", "notify-events-default", "notify-events-supported", "notify-lease-duration-default", "notify-lease-duration-supported", "notify-max-events-supported", "notify-pull-method-supported", "operations-supported", "pdl-override-supported", "preferred-attributes-supported", "printer-alert", "printer-alert-description", "printer-camera-image-uri", "printer-charge-info", "printer-charge-info-uri", "printer-config-change-date-time", "printer-config-change-time", "printer-current-time", "printer-detailed-status-messages", "printer-dns-sd-name", "printer-fax-log-uri", "printer-geo-location", "printer-get-attributes-supported", "printer-icons", "printer-id", "printer-info", "printer-is-accepting-jobs", "printer-location", "printer-message-date-time", "printer-message-from-operator", "printer-message-time", "printer-more-info", "printer-name", "printer-organization", "printer-organizational-unit", "printer-service-type", "printer-settable-attributes-supported", "printer-state", "printer-state-change-date-time", "printer-state-change-time", "printer-state-message", "printer-state-reasons", "printer-static-resource-directory-uri", "printer-static-resource-k-octets-free", "printer-static-resource-k-octets-supported", "printer-strings-languages-supported", "printer-strings-uri", "printer-supply-info-uri", "printer-up-time", "printer-uri-supported", "printer-uuid", "printer-xri-supported", "queued-job-count", "reference-uri-schemes-supported", "requesting-user-uri-supported", "uri-authentication-supported", "uri-security-supported", "which-jobs-supported", "xri-authentication-supported", "xri-security-supported", "xri-uri-scheme-supported" }; (void)f; (void)user_data; for (i = 0; i < (sizeof(ignored) / sizeof(ignored[0])); i ++) { if (!strcmp(attr, ignored[i])) return (false); } return (true); } // // 'ippfile_error_cb()' - Log an error message. // static bool // O - `true` to continue, `false` to stop ippfile_error_cb( ipp_file_t *f, // I - IPP file data void *user_data, // I - User data pointer (unused) const char *error) // I - Error message { (void)f; (void)user_data; _cupsLangPrintf(stderr, "%s\n", error); return (false); } // // 'load_ippfile_attributes()' - Load IPP attributes from an ippserver file. // static ipp_t * // O - IPP attributes or `NULL` on error load_ippfile_attributes( const char *servername, // I - Server name or `NULL` for default int serverport, // I - Server port number const char *filename, // I - ippserver attribute filename cups_array_t *docformats) // I - document-format-supported values { ipp_file_t *file; // IPP data file ipp_t *attrs; // IPP attributes char temp[256]; // Temporary string (void)docformats; // for now // Setup callbacks and variables for the printer configuration file... // // The following additional variables are supported: // // - SERVERNAME: The host name of the server. // - SERVERPORT: The default port of the server. attrs = ippNew(); file = ippFileNew(NULL, (ipp_fattr_cb_t)ippfile_attr_cb, (ipp_ferror_cb_t)ippfile_error_cb, NULL); ippFileSetAttributes(file, attrs); ippFileSetGroupTag(file, IPP_TAG_PRINTER); if (servername) { ippFileSetVar(file, "SERVERNAME", servername); } else { httpGetHostname(NULL, temp, sizeof(temp)); ippFileSetVar(file, "SERVERNAME", temp); } snprintf(temp, sizeof(temp), "%d", serverport); ippFileSetVar(file, "SERVERPORT", temp); // Load attributes and values for the printer... ippFileOpen(file, filename, "r"); ippFileRead(file, NULL, false); // Free memory and return... ippFileDelete(file); return (attrs); } // // 'load_legacy_attributes()' - Load IPP attributes using the old ippserver // options. // static ipp_t * // O - IPP attributes or `NULL` on error load_legacy_attributes( const char *make, // I - Manufacturer name const char *model, // I - Model name int ppm, // I - pages-per-minute int ppm_color, // I - pages-per-minute-color int duplex, // I - Duplex support? cups_array_t *docformats) // I - document-format-supported values { size_t i; // Looping var ipp_t *attrs, // IPP attributes *col; // Collection value ipp_attribute_t *attr; // Current attribute char device_id[1024],// printer-device-id *ptr, // Pointer into device ID make_model[128];// printer-make-and-model const char *format, // Current document format *prefix; // Prefix for device ID size_t num_media; // Number of media const char * const *media; // List of media size_t num_ready; // Number of loaded media const char * const *ready; // List of loaded media pwg_media_t *pwg; // PWG media size information static const char * const media_supported[] = { // media-supported values "na_letter_8.5x11in", // Letter "na_legal_8.5x14in", // Legal "iso_a4_210x297mm", // A4 "na_number-10_4.125x9.5in", // #10 Envelope "iso_dl_110x220mm" // DL Envelope }; static const char * const media_supported_color[] = { // media-supported values "na_letter_8.5x11in", // Letter "na_legal_8.5x14in", // Legal "iso_a4_210x297mm", // A4 "na_number-10_4.125x9.5in", // #10 Envelope "iso_dl_110x220mm", // DL Envelope "na_index-3x5_3x5in", // Photo 3x5 "oe_photo-l_3.5x5in", // Photo L "na_index-4x6_4x6in", // Photo 4x6 "iso_a6_105x148mm", // A6 "na_5x7_5x7in", // Photo 5x7 aka 2L "iso_a5_148x210mm", // A5 "roll_min_4x1in", // Roll "roll_max_8.5x39.6in" // Roll }; static const char * const media_ready[] = { // media-ready values "na_letter_8.5x11in", // Letter "na_number-10_4.125x9.5in" // #10 }; static const char * const media_ready_color[] = { // media-ready values "na_letter_8.5x11in", // Letter "na_index-4x6_4x6in", // Photo 4x6 "roll_current_8.5x0in" // 8.5" roll }; static const char * const media_source_supported[] = { // media-source-supported values "auto", "main", "manual", "by-pass-tray" // AKA multi-purpose tray }; static const char * const media_source_supported_color[] = { // media-source-supported values "auto", "main", "photo", "roll" }; static const char * const media_type_supported[] = { // media-type-supported values "auto", "cardstock", "envelope", "labels", "other", "stationery", "stationery-letterhead", "transparency" }; static const char * const media_type_supported_color[] = { // media-type-supported values "auto", "cardstock", "envelope", "labels", "other", "stationery", "stationery-letterhead", "transparency", "photographic-glossy", "photographic-high-gloss", "photographic-matte", "photographic-satin", "photographic-semi-gloss" }; static const int media_bottom_margin_supported[] = { // media-bottom-margin-supported values 635 // 1/4" }; static const int media_bottom_margin_supported_color[] = { // media-bottom/top-margin-supported values 0, // Borderless 1168 // 0.46" (common HP inkjet bottom margin) }; static const int media_lr_margin_supported[] = { // media-left/right-margin-supported values 340, // 3.4mm (historical HP PCL A4 margin) 635 // 1/4" }; static const int media_lr_margin_supported_color[] = { // media-left/right-margin-supported values 0, // Borderless 340, // 3.4mm (historical HP PCL A4 margin) 635 // 1/4" }; static const int media_top_margin_supported[] = { // media-top-margin-supported values 635 // 1/4" }; static const int media_top_margin_supported_color[] = { // media-top/top-margin-supported values 0, // Borderless 102 // 0.04" (common HP inkjet top margin }; static const int orientation_requested_supported[4] = { // orientation-requested-supported values IPP_ORIENT_PORTRAIT, IPP_ORIENT_LANDSCAPE, IPP_ORIENT_REVERSE_LANDSCAPE, IPP_ORIENT_REVERSE_PORTRAIT }; static const char * const overrides_supported[] = { // overrides-supported values "document-numbers", "media", "media-col", "orientation-requested", "pages" }; static const char * const print_color_mode_supported[] = { // print-color-mode-supported values "monochrome" }; static const char * const print_color_mode_supported_color[] = { // print-color-mode-supported values "auto", "color", "monochrome" }; static const int print_quality_supported[] = { // print-quality-supported values IPP_QUALITY_DRAFT, IPP_QUALITY_NORMAL, IPP_QUALITY_HIGH }; static const char * const printer_input_tray[] = { // printer-input-tray values "type=sheetFeedAutoRemovableTray;mediafeed=0;mediaxfeed=0;maxcapacity=-2;level=-2;status=0;name=auto", "type=sheetFeedAutoRemovableTray;mediafeed=0;mediaxfeed=0;maxcapacity=250;level=100;status=0;name=main", "type=sheetFeedManual;mediafeed=0;mediaxfeed=0;maxcapacity=1;level=-2;status=0;name=manual", "type=sheetFeedAutoNonRemovableTray;mediafeed=0;mediaxfeed=0;maxcapacity=25;level=-2;status=0;name=by-pass-tray" }; static const char * const printer_input_tray_color[] = { // printer-input-tray values "type=sheetFeedAutoRemovableTray;mediafeed=0;mediaxfeed=0;maxcapacity=-2;level=-2;status=0;name=auto", "type=sheetFeedAutoRemovableTray;mediafeed=0;mediaxfeed=0;maxcapacity=250;level=-2;status=0;name=main", "type=sheetFeedAutoRemovableTray;mediafeed=0;mediaxfeed=0;maxcapacity=25;level=-2;status=0;name=photo", "type=continuousRoll;mediafeed=0;mediaxfeed=0;maxcapacity=100;level=-2;status=0;name=roll" }; static const char * const printer_supply[] = { // printer-supply values "index=1;class=receptacleThatIsFilled;type=wasteToner;unit=percent;" "maxcapacity=100;level=25;colorantname=unknown;", "index=2;class=supplyThatIsConsumed;type=toner;unit=percent;" "maxcapacity=100;level=75;colorantname=black;" }; static const char * const printer_supply_color[] = { // printer-supply values "index=1;class=receptacleThatIsFilled;type=wasteInk;unit=percent;" "maxcapacity=100;level=25;colorantname=unknown;", "index=2;class=supplyThatIsConsumed;type=ink;unit=percent;" "maxcapacity=100;level=75;colorantname=black;", "index=3;class=supplyThatIsConsumed;type=ink;unit=percent;" "maxcapacity=100;level=50;colorantname=cyan;", "index=4;class=supplyThatIsConsumed;type=ink;unit=percent;" "maxcapacity=100;level=33;colorantname=magenta;", "index=5;class=supplyThatIsConsumed;type=ink;unit=percent;" "maxcapacity=100;level=67;colorantname=yellow;" }; static const char * const printer_supply_description[] = { // printer-supply-description values "Toner Waste Tank", "Black Toner" }; static const char * const printer_supply_description_color[] = { // printer-supply-description values "Ink Waste Tank", "Black Ink", "Cyan Ink", "Magenta Ink", "Yellow Ink" }; static const int pwg_raster_document_resolution_supported[] = { 300, 600 }; static const char * const pwg_raster_document_type_supported[] = { "black_1", "sgray_8" }; static const char * const pwg_raster_document_type_supported_color[] = { "black_1", "sgray_8", "srgb_8", "srgb_16" }; static const char * const sides_supported[] = { // sides-supported values "one-sided", "two-sided-long-edge", "two-sided-short-edge" }; static const char * const urf_supported[] = { // urf-supported values "CP1", "IS1-4-5-19", "MT1-2-3-4-5-6", "RS600", "V1.4", "W8" }; static const char * const urf_supported_color[] = { // urf-supported values "CP1", "IS1-4-5-7-19", "MT1-2-3-4-5-6-8-9-10-11-12-13", "RS600", "SRGB24", "V1.4", "W8" }; static const char * const urf_supported_color_duplex[] = { // urf-supported values "CP1", "IS1-4-5-7-19", "MT1-2-3-4-5-6-8-9-10-11-12-13", "RS600", "SRGB24", "V1.4", "W8", "DM3" }; static const char * const urf_supported_duplex[] = { // urf-supported values "CP1", "IS1-4-5-19", "MT1-2-3-4-5-6", "RS600", "V1.4", "W8", "DM1" }; attrs = ippNew(); if (ppm_color > 0) { num_media = (int)(sizeof(media_supported_color) / sizeof(media_supported_color[0])); media = media_supported_color; num_ready = (int)(sizeof(media_ready_color) / sizeof(media_ready_color[0])); ready = media_ready_color; } else { num_media = (int)(sizeof(media_supported) / sizeof(media_supported[0])); media = media_supported; num_ready = (int)(sizeof(media_ready) / sizeof(media_ready[0])); ready = media_ready; } // color-supported ippAddBoolean(attrs, IPP_TAG_PRINTER, "color-supported", ppm_color > 0); // copies-default ippAddInteger(attrs, IPP_TAG_PRINTER, IPP_TAG_INTEGER, "copies-default", 1); // copies-supported ippAddRange(attrs, IPP_TAG_PRINTER, "copies-supported", 1, (cupsArrayFind(docformats, (void *)"application/pdf") != NULL || cupsArrayFind(docformats, (void *)"image/jpeg") != NULL) ? 999 : 1); // document-password-supported if (cupsArrayFind(docformats, (void *)"application/pdf")) ippAddInteger(attrs, IPP_TAG_PRINTER, IPP_TAG_INTEGER, "document-password-supported", 1023); // finishing-template-supported ippAddString(attrs, IPP_TAG_PRINTER, IPP_CONST_TAG(IPP_TAG_KEYWORD), "finishing-template-supported", NULL, "none"); // finishings-col-database col = ippNew(); ippAddString(col, IPP_TAG_PRINTER, IPP_CONST_TAG(IPP_TAG_KEYWORD), "finishing-template", NULL, "none"); ippAddCollection(attrs, IPP_TAG_PRINTER, "finishings-col-database", col); ippDelete(col); // finishings-col-default col = ippNew(); ippAddString(col, IPP_TAG_PRINTER, IPP_CONST_TAG(IPP_TAG_KEYWORD), "finishing-template", NULL, "none"); ippAddCollection(attrs, IPP_TAG_PRINTER, "finishings-col-default", col); ippDelete(col); // finishings-col-ready col = ippNew(); ippAddString(col, IPP_TAG_PRINTER, IPP_CONST_TAG(IPP_TAG_KEYWORD), "finishing-template", NULL, "none"); ippAddCollection(attrs, IPP_TAG_PRINTER, "finishings-col-ready", col); ippDelete(col); // finishings-col-supported ippAddString(attrs, IPP_TAG_PRINTER, IPP_CONST_TAG(IPP_TAG_KEYWORD), "finishings-col-supported", NULL, "finishing-template"); // finishings-default ippAddInteger(attrs, IPP_TAG_PRINTER, IPP_TAG_ENUM, "finishings-default", IPP_FINISHINGS_NONE); // finishings-ready ippAddInteger(attrs, IPP_TAG_PRINTER, IPP_TAG_ENUM, "finishings-ready", IPP_FINISHINGS_NONE); // finishings-supported ippAddInteger(attrs, IPP_TAG_PRINTER, IPP_TAG_ENUM, "finishings-supported", IPP_FINISHINGS_NONE); // media-bottom-margin-supported if (ppm_color > 0) ippAddIntegers(attrs, IPP_TAG_PRINTER, IPP_TAG_INTEGER, "media-bottom-margin-supported", (int)(sizeof(media_bottom_margin_supported) / sizeof(media_bottom_margin_supported[0])), media_bottom_margin_supported); else ippAddIntegers(attrs, IPP_TAG_PRINTER, IPP_TAG_INTEGER, "media-bottom-margin-supported", (int)(sizeof(media_bottom_margin_supported_color) / sizeof(media_bottom_margin_supported_color[0])), media_bottom_margin_supported_color); // media-col-database and media-col-default for (i = 0, attr = NULL; i < num_media; i ++) { int bottom, left, // media-xxx-margins right, top; const char *source; // media-source, if any if ((pwg = pwgMediaForPWG(media[i])) == NULL) continue; if (pwg->width < 21000 && pwg->length < 21000) { source = "photo"; // Photo size media from photo tray bottom = // Borderless margins left = right = top = 0; } else if (pwg->width < 21000) { source = "by-pass-tray"; // Envelopes from multi-purpose tray bottom = ppm_color > 0 ? media_bottom_margin_supported_color[1] : media_bottom_margin_supported[0]; left = // Left/right margins are standard right = media_lr_margin_supported[1]; top = ppm_color > 0 ? media_top_margin_supported_color[1] : media_top_margin_supported[0]; } else if (pwg->width == 21000) { source = NULL; // A4 from any tray bottom = ppm_color > 0 ? media_bottom_margin_supported_color[1] : media_bottom_margin_supported[0]; left = // Left/right margins are reduced right = media_lr_margin_supported[0]; top = ppm_color > 0 ? media_top_margin_supported_color[1] : media_top_margin_supported[0]; } else { source = NULL; // Other size media from any tray bottom = ppm_color > 0 ? media_bottom_margin_supported_color[1] : media_bottom_margin_supported[0]; left = // Left/right margins are standard right = media_lr_margin_supported[1]; top = ppm_color > 0 ? media_top_margin_supported_color[1] : media_top_margin_supported[0]; } if (!strncmp(media[i], "roll_min_", 9) && i < (num_media - 1)) { // Roll min/max range... pwg_media_t *pwg2; // Max size ipp_t *media_size; // media-size member attribute i ++; if ((pwg2 = pwgMediaForPWG(media[i])) == NULL) continue; media_size = ippNew(); ippAddRange(media_size, IPP_TAG_ZERO, "x-dimension", pwg->width, pwg2->width); ippAddRange(media_size, IPP_TAG_ZERO, "y-dimension", pwg->length, pwg2->length); col = create_media_col(NULL, source, NULL, media_size, bottom, left, right, top); } else { // Sheet size col = create_media_col(media[i], source, NULL, create_media_size(pwg->width, pwg->length), bottom, left, right, top); } if (attr) ippSetCollection(attrs, &attr, ippGetCount(attr), col); else attr = ippAddCollection(attrs, IPP_TAG_PRINTER, "media-col-database", col); ippDelete(col); } // media-col-default if ((pwg = pwgMediaForPWG(ready[0])) != NULL) { if (pwg->width == 21000) col = create_media_col(ready[0], "main", "stationery", create_media_size(pwg->width, pwg->length), ppm_color > 0 ? media_bottom_margin_supported_color[1] : media_bottom_margin_supported[0], media_lr_margin_supported[0], media_lr_margin_supported[0], ppm_color > 0 ? media_top_margin_supported_color[1] : media_top_margin_supported[0]); else col = create_media_col(ready[0], "main", "stationery", create_media_size(pwg->width, pwg->length), ppm_color > 0 ? media_bottom_margin_supported_color[1] : media_bottom_margin_supported[0], media_lr_margin_supported[1], media_lr_margin_supported[1], ppm_color > 0 ? media_top_margin_supported_color[1] : media_top_margin_supported[0]); ippAddCollection(attrs, IPP_TAG_PRINTER, "media-col-default", col); ippDelete(col); } // media-col-ready for (i = 0, attr = NULL; i < num_ready; i ++) { int bottom, left, // media-xxx-margins right, top; const char *source, // media-source *type; // media-type if ((pwg = pwgMediaForPWG(ready[i])) == NULL) continue; if (pwg->width < 21000 && pwg->length < 21000) { source = "photo"; // Photo size media from photo tray type = "photographic-glossy"; // Glossy photo paper bottom = // Borderless margins left = right = top = 0; } else if (pwg->width < 21000) { source = "by-pass-tray"; // Envelopes from multi-purpose tray type = "envelope"; // Envelope bottom = ppm_color > 0 ? media_bottom_margin_supported_color[1] : media_bottom_margin_supported[0]; left = // Left/right margins are standard right = media_lr_margin_supported[1]; top = ppm_color > 0 ? media_top_margin_supported_color[1] : media_top_margin_supported[0]; } else if (pwg->width == 21000) { source = "main"; // A4 from main tray type = "stationery"; // Plain paper bottom = ppm_color > 0 ? media_bottom_margin_supported_color[1] : media_bottom_margin_supported[0]; left = // Left/right margins are reduced right = media_lr_margin_supported[0]; top = ppm_color > 0 ? media_top_margin_supported_color[1] : media_top_margin_supported[0]; } else { source = "main"; // A4 from main tray type = "stationery"; // Plain paper bottom = ppm_color > 0 ? media_bottom_margin_supported_color[1] : media_bottom_margin_supported[0]; left = // Left/right margins are standard right = media_lr_margin_supported[1]; top = ppm_color > 0 ? media_top_margin_supported_color[1] : media_top_margin_supported[0]; } col = create_media_col(ready[i], source, type, create_media_size(pwg->width, pwg->length), bottom, left, right, top); if (attr) ippSetCollection(attrs, &attr, ippGetCount(attr), col); else attr = ippAddCollection(attrs, IPP_TAG_PRINTER, "media-col-ready", col); ippDelete(col); } // media-default ippAddString(attrs, IPP_TAG_PRINTER, IPP_CONST_TAG(IPP_TAG_KEYWORD), "media-default", NULL, media[0]); // media-left/right-margin-supported if (ppm_color > 0) { ippAddIntegers(attrs, IPP_TAG_PRINTER, IPP_TAG_INTEGER, "media-left-margin-supported", (int)(sizeof(media_lr_margin_supported_color) / sizeof(media_lr_margin_supported_color[0])), media_lr_margin_supported_color); ippAddIntegers(attrs, IPP_TAG_PRINTER, IPP_TAG_INTEGER, "media-right-margin-supported", (int)(sizeof(media_lr_margin_supported_color) / sizeof(media_lr_margin_supported_color[0])), media_lr_margin_supported_color); } else { ippAddIntegers(attrs, IPP_TAG_PRINTER, IPP_TAG_INTEGER, "media-left-margin-supported", (int)(sizeof(media_lr_margin_supported) / sizeof(media_lr_margin_supported[0])), media_lr_margin_supported); ippAddIntegers(attrs, IPP_TAG_PRINTER, IPP_TAG_INTEGER, "media-right-margin-supported", (int)(sizeof(media_lr_margin_supported) / sizeof(media_lr_margin_supported[0])), media_lr_margin_supported); } // media-ready ippAddStrings(attrs, IPP_TAG_PRINTER, IPP_TAG_KEYWORD, "media-ready", num_ready, NULL, ready); // media-supported ippAddStrings(attrs, IPP_TAG_PRINTER, IPP_CONST_TAG(IPP_TAG_KEYWORD), "media-supported", num_media, NULL, media); // media-size-supported for (i = 0, attr = NULL; i < num_media; i ++) { if ((pwg = pwgMediaForPWG(media[i])) == NULL) continue; if (!strncmp(media[i], "roll_min_", 9) && i < (num_media - 1)) { // Roll min/max range... pwg_media_t *pwg2; // Max size i ++; if ((pwg2 = pwgMediaForPWG(media[i])) == NULL) continue; col = create_media_size_range(pwg->width, pwg2->width, pwg->length, pwg2->length); } else { // Sheet size... col = create_media_size(pwg->width, pwg->length); } if (attr) ippSetCollection(attrs, &attr, ippGetCount(attr), col); else attr = ippAddCollection(attrs, IPP_TAG_PRINTER, "media-size-supported", col); ippDelete(col); } // media-source-supported if (ppm_color > 0) ippAddStrings(attrs, IPP_TAG_PRINTER, IPP_CONST_TAG(IPP_TAG_KEYWORD), "media-source-supported", (int)(sizeof(media_source_supported_color) / sizeof(media_source_supported_color[0])), NULL, media_source_supported_color); else ippAddStrings(attrs, IPP_TAG_PRINTER, IPP_CONST_TAG(IPP_TAG_KEYWORD), "media-source-supported", (int)(sizeof(media_source_supported) / sizeof(media_source_supported[0])), NULL, media_source_supported); // media-top-margin-supported if (ppm_color > 0) ippAddIntegers(attrs, IPP_TAG_PRINTER, IPP_TAG_INTEGER, "media-top-margin-supported", (int)(sizeof(media_top_margin_supported) / sizeof(media_top_margin_supported[0])), media_top_margin_supported); else ippAddIntegers(attrs, IPP_TAG_PRINTER, IPP_TAG_INTEGER, "media-top-margin-supported", (int)(sizeof(media_top_margin_supported_color) / sizeof(media_top_margin_supported_color[0])), media_top_margin_supported_color); // media-type-supported if (ppm_color > 0) ippAddStrings(attrs, IPP_TAG_PRINTER, IPP_CONST_TAG(IPP_TAG_KEYWORD), "media-type-supported", (int)(sizeof(media_type_supported_color) / sizeof(media_type_supported_color[0])), NULL, media_type_supported_color); else ippAddStrings(attrs, IPP_TAG_PRINTER, IPP_CONST_TAG(IPP_TAG_KEYWORD), "media-type-supported", (int)(sizeof(media_type_supported) / sizeof(media_type_supported[0])), NULL, media_type_supported); // orientation-requested-default ippAddInteger(attrs, IPP_TAG_PRINTER, IPP_TAG_ENUM, "orientation-requested-default", IPP_ORIENT_PORTRAIT); // orientation-requested-supported if (cupsArrayFind(docformats, (void *)"application/pdf") || cupsArrayFind(docformats, (void *)"image/jpeg")) ippAddIntegers(attrs, IPP_TAG_PRINTER, IPP_TAG_ENUM, "orientation-requested-supported", (int)(sizeof(orientation_requested_supported) / sizeof(orientation_requested_supported[0])), orientation_requested_supported); else ippAddInteger(attrs, IPP_TAG_PRINTER, IPP_TAG_ENUM, "orientation-requested-supported", IPP_ORIENT_PORTRAIT); // output-bin-default if (ppm_color > 0) ippAddString(attrs, IPP_TAG_PRINTER, IPP_CONST_TAG(IPP_TAG_KEYWORD), "output-bin-default", NULL, "face-up"); else ippAddString(attrs, IPP_TAG_PRINTER, IPP_CONST_TAG(IPP_TAG_KEYWORD), "output-bin-default", NULL, "face-down"); // output-bin-supported if (ppm_color > 0) ippAddString(attrs, IPP_TAG_PRINTER, IPP_CONST_TAG(IPP_TAG_KEYWORD), "output-bin-supported", NULL, "face-up"); else ippAddString(attrs, IPP_TAG_PRINTER, IPP_CONST_TAG(IPP_TAG_KEYWORD), "output-bin-supported", NULL, "face-down"); // overrides-supported if (cupsArrayFind(docformats, (void *)"application/pdf")) ippAddStrings(attrs, IPP_TAG_PRINTER, IPP_TAG_KEYWORD, "overrides-supported", (int)(sizeof(overrides_supported) / sizeof(overrides_supported[0])), NULL, overrides_supported); // page-ranges-supported ippAddBoolean(attrs, IPP_TAG_PRINTER, "page-ranges-supported", cupsArrayFind(docformats, (void *)"application/pdf") != NULL); // pages-per-minute ippAddInteger(attrs, IPP_TAG_PRINTER, IPP_TAG_INTEGER, "pages-per-minute", ppm); // pages-per-minute-color if (ppm_color > 0) ippAddInteger(attrs, IPP_TAG_PRINTER, IPP_TAG_INTEGER, "pages-per-minute-color", ppm_color); // print-color-mode-default ippAddString(attrs, IPP_TAG_PRINTER, IPP_CONST_TAG(IPP_TAG_KEYWORD), "print-color-mode-default", NULL, ppm_color > 0 ? "auto" : "monochrome"); // print-color-mode-supported if (ppm_color > 0) ippAddStrings(attrs, IPP_TAG_PRINTER, IPP_CONST_TAG(IPP_TAG_KEYWORD), "print-color-mode-supported", (int)(sizeof(print_color_mode_supported_color) / sizeof(print_color_mode_supported_color[0])), NULL, print_color_mode_supported_color); else ippAddStrings(attrs, IPP_TAG_PRINTER, IPP_CONST_TAG(IPP_TAG_KEYWORD), "print-color-mode-supported", (int)(sizeof(print_color_mode_supported) / sizeof(print_color_mode_supported[0])), NULL, print_color_mode_supported); // print-content-optimize-default ippAddString(attrs, IPP_TAG_PRINTER, IPP_CONST_TAG(IPP_TAG_KEYWORD), "print-content-optimize-default", NULL, "auto"); // print-content-optimize-supported ippAddString(attrs, IPP_TAG_PRINTER, IPP_CONST_TAG(IPP_TAG_KEYWORD), "print-content-optimize-supported", NULL, "auto"); // print-quality-default ippAddInteger(attrs, IPP_TAG_PRINTER, IPP_TAG_ENUM, "print-quality-default", IPP_QUALITY_NORMAL); // print-quality-supported ippAddIntegers(attrs, IPP_TAG_PRINTER, IPP_TAG_ENUM, "print-quality-supported", (int)(sizeof(print_quality_supported) / sizeof(print_quality_supported[0])), print_quality_supported); // print-rendering-intent-default ippAddString(attrs, IPP_TAG_PRINTER, IPP_CONST_TAG(IPP_TAG_KEYWORD), "print-rendering-intent-default", NULL, "auto"); // print-rendering-intent-supported ippAddString(attrs, IPP_TAG_PRINTER, IPP_CONST_TAG(IPP_TAG_KEYWORD), "print-rendering-intent-supported", NULL, "auto"); // printer-device-id snprintf(device_id, sizeof(device_id), "MFG:%s;MDL:%s;", make, model); ptr = device_id + strlen(device_id); prefix = "CMD:"; for (format = (const char *)cupsArrayGetFirst(docformats); format; format = (const char *)cupsArrayGetNext(docformats)) { if (!strcasecmp(format, "application/pdf")) snprintf(ptr, sizeof(device_id) - (size_t)(ptr - device_id), "%sPDF", prefix); else if (!strcasecmp(format, "application/postscript")) snprintf(ptr, sizeof(device_id) - (size_t)(ptr - device_id), "%sPS", prefix); else if (!strcasecmp(format, "application/vnd.hp-PCL")) snprintf(ptr, sizeof(device_id) - (size_t)(ptr - device_id), "%sPCL", prefix); else if (!strcasecmp(format, "image/jpeg")) snprintf(ptr, sizeof(device_id) - (size_t)(ptr - device_id), "%sJPEG", prefix); else if (!strcasecmp(format, "image/png")) snprintf(ptr, sizeof(device_id) - (size_t)(ptr - device_id), "%sPNG", prefix); else if (!strcasecmp(format, "image/pwg-raster")) snprintf(ptr, sizeof(device_id) - (size_t)(ptr - device_id), "%sPWG", prefix); else if (!strcasecmp(format, "image/urf")) snprintf(ptr, sizeof(device_id) - (size_t)(ptr - device_id), "%sURF", prefix); else continue; ptr += strlen(ptr); prefix = ","; } if (ptr < (device_id + sizeof(device_id) - 1)) { *ptr++ = ';'; *ptr = '\0'; } ippAddString(attrs, IPP_TAG_PRINTER, IPP_TAG_TEXT, "printer-device-id", NULL, device_id); // printer-input-tray if (ppm_color > 0) { attr = ippAddOctetString(attrs, IPP_TAG_PRINTER, "printer-input-tray", printer_input_tray_color[0], strlen(printer_input_tray_color[0])); for (i = 1; i < (int)(sizeof(printer_input_tray_color) / sizeof(printer_input_tray_color[0])); i ++) ippSetOctetString(attrs, &attr, i, printer_input_tray_color[i], strlen(printer_input_tray_color[i])); } else { attr = ippAddOctetString(attrs, IPP_TAG_PRINTER, "printer-input-tray", printer_input_tray[0], strlen(printer_input_tray[0])); for (i = 1; i < (int)(sizeof(printer_input_tray) / sizeof(printer_input_tray[0])); i ++) ippSetOctetString(attrs, &attr, i, printer_input_tray[i], strlen(printer_input_tray[i])); } // printer-make-and-model snprintf(make_model, sizeof(make_model), "%s %s", make, model); ippAddString(attrs, IPP_TAG_PRINTER, IPP_TAG_TEXT, "printer-make-and-model", NULL, make_model); // printer-resolution-default ippAddResolution(attrs, IPP_TAG_PRINTER, "printer-resolution-default", IPP_RES_PER_INCH, 600, 600); // printer-resolution-supported ippAddResolution(attrs, IPP_TAG_PRINTER, "printer-resolution-supported", IPP_RES_PER_INCH, 600, 600); // printer-supply and printer-supply-description if (ppm_color > 0) { attr = ippAddOctetString(attrs, IPP_TAG_PRINTER, "printer-supply", printer_supply_color[0], strlen(printer_supply_color[0])); for (i = 1; i < (int)(sizeof(printer_supply_color) / sizeof(printer_supply_color[0])); i ++) ippSetOctetString(attrs, &attr, i, printer_supply_color[i], strlen(printer_supply_color[i])); ippAddStrings(attrs, IPP_TAG_PRINTER, IPP_CONST_TAG(IPP_TAG_TEXT), "printer-supply-description", (int)(sizeof(printer_supply_description_color) / sizeof(printer_supply_description_color[0])), NULL, printer_supply_description_color); } else { attr = ippAddOctetString(attrs, IPP_TAG_PRINTER, "printer-supply", printer_supply[0], strlen(printer_supply[0])); for (i = 1; i < (int)(sizeof(printer_supply) / sizeof(printer_supply[0])); i ++) ippSetOctetString(attrs, &attr, i, printer_supply[i], strlen(printer_supply[i])); ippAddStrings(attrs, IPP_TAG_PRINTER, IPP_CONST_TAG(IPP_TAG_TEXT), "printer-supply-description", (int)(sizeof(printer_supply_description) / sizeof(printer_supply_description[0])), NULL, printer_supply_description); } // pwg-raster-document-xxx-supported if (cupsArrayFind(docformats, (void *)"image/pwg-raster")) { ippAddResolutions(attrs, IPP_TAG_PRINTER, "pwg-raster-document-resolution-supported", (int)(sizeof(pwg_raster_document_resolution_supported) / sizeof(pwg_raster_document_resolution_supported[0])), IPP_RES_PER_INCH, pwg_raster_document_resolution_supported, pwg_raster_document_resolution_supported); if (ppm_color > 0 && duplex) ippAddString(attrs, IPP_TAG_PRINTER, IPP_CONST_TAG(IPP_TAG_KEYWORD), "pwg-raster-document-sheet-back", NULL, "rotated"); else if (duplex) ippAddString(attrs, IPP_TAG_PRINTER, IPP_CONST_TAG(IPP_TAG_KEYWORD), "pwg-raster-document-sheet-back", NULL, "normal"); if (ppm_color > 0) ippAddStrings(attrs, IPP_TAG_PRINTER, IPP_CONST_TAG(IPP_TAG_KEYWORD), "pwg-raster-document-type-supported", (int)(sizeof(pwg_raster_document_type_supported_color) / sizeof(pwg_raster_document_type_supported_color[0])), NULL, pwg_raster_document_type_supported_color); else ippAddStrings(attrs, IPP_TAG_PRINTER, IPP_CONST_TAG(IPP_TAG_KEYWORD), "pwg-raster-document-type-supported", (int)(sizeof(pwg_raster_document_type_supported) / sizeof(pwg_raster_document_type_supported[0])), NULL, pwg_raster_document_type_supported); } // sides-default ippAddString(attrs, IPP_TAG_PRINTER, IPP_CONST_TAG(IPP_TAG_KEYWORD), "sides-default", NULL, "one-sided"); // sides-supported if (duplex) ippAddStrings(attrs, IPP_TAG_PRINTER, IPP_CONST_TAG(IPP_TAG_KEYWORD), "sides-supported", (int)(sizeof(sides_supported) / sizeof(sides_supported[0])), NULL, sides_supported); else ippAddString(attrs, IPP_TAG_PRINTER, IPP_CONST_TAG(IPP_TAG_KEYWORD), "sides-supported", NULL, "one-sided"); // urf-supported if (cupsArrayFind(docformats, (void *)"image/urf")) { if (ppm_color > 0) { if (duplex) ippAddStrings(attrs, IPP_TAG_PRINTER, IPP_TAG_KEYWORD, "urf-supported", (int)(sizeof(urf_supported_color_duplex) / sizeof(urf_supported_color_duplex[0])), NULL, urf_supported_color_duplex); else ippAddStrings(attrs, IPP_TAG_PRINTER, IPP_TAG_KEYWORD, "urf-supported", (int)(sizeof(urf_supported_color) / sizeof(urf_supported_color[0])), NULL, urf_supported_color); } else if (duplex) { ippAddStrings(attrs, IPP_TAG_PRINTER, IPP_TAG_KEYWORD, "urf-supported", (int)(sizeof(urf_supported_duplex) / sizeof(urf_supported_duplex[0])), NULL, urf_supported_duplex); } else { ippAddStrings(attrs, IPP_TAG_PRINTER, IPP_TAG_KEYWORD, "urf-supported", (int)(sizeof(urf_supported) / sizeof(urf_supported[0])), NULL, urf_supported); } } return (attrs); } #if !CUPS_LITE // // 'load_ppd_attributes()' - Load IPP attributes from a PPD file. // static ipp_t * // O - IPP attributes or `NULL` on error load_ppd_attributes( const char *ppdfile, // I - PPD filename cups_array_t *docformats) // I - document-format-supported values { int i, j; // Looping vars ipp_t *attrs; // Attributes ipp_attribute_t *attr; // Current attribute ipp_t *col, // Current collection value *size; // Current media-size collection value ppd_file_t *ppd; // PPD data ppd_attr_t *ppd_attr; // PPD attribute ppd_choice_t *ppd_choice; // PPD choice ppd_size_t *ppd_size; // Default PPD size pwg_size_t *pwg_size, // Current PWG size *default_size = NULL; // Default PWG size const char *default_source = NULL, // Default media source *default_type = NULL; // Default media type pwg_map_t *pwg_map; // Mapping from PWG to PPD keywords _ppd_cache_t *pc; // PPD cache _pwg_finishings_t *finishings; // Current finishings value const char *template; // Current finishings-template value int num_margins; // Number of media-xxx-margin-supported values int margins[10]; // media-xxx-margin-supported values int xres, // Default horizontal resolution yres; // Default vertical resolution int num_urf; // Number of urf-supported values const char *urf[10]; // urf-supported values char urf_rs[32]; // RS value static const int orientation_requested_supported[4] = { // orientation-requested-supported values IPP_ORIENT_PORTRAIT, IPP_ORIENT_LANDSCAPE, IPP_ORIENT_REVERSE_LANDSCAPE, IPP_ORIENT_REVERSE_PORTRAIT }; static const char * const overrides_supported[] = { // overrides-supported "document-numbers", "media", "media-col", "orientation-requested", "pages" }; static const char * const print_color_mode_supported[] = { // print-color-mode-supported values "monochrome" }; static const char * const print_color_mode_supported_color[] = { // print-color-mode-supported values "auto", "color", "monochrome" }; static const int print_quality_supported[] = { // print-quality-supported values IPP_QUALITY_DRAFT, IPP_QUALITY_NORMAL, IPP_QUALITY_HIGH }; static const char * const printer_supply[] = { // printer-supply values "index=1;class=receptacleThatIsFilled;type=wasteToner;unit=percent;" "maxcapacity=100;level=25;colorantname=unknown;", "index=2;class=supplyThatIsConsumed;type=toner;unit=percent;" "maxcapacity=100;level=75;colorantname=black;" }; static const char * const printer_supply_color[] = { // printer-supply values "index=1;class=receptacleThatIsFilled;type=wasteInk;unit=percent;" "maxcapacity=100;level=25;colorantname=unknown;", "index=2;class=supplyThatIsConsumed;type=ink;unit=percent;" "maxcapacity=100;level=75;colorantname=black;", "index=3;class=supplyThatIsConsumed;type=ink;unit=percent;" "maxcapacity=100;level=50;colorantname=cyan;", "index=4;class=supplyThatIsConsumed;type=ink;unit=percent;" "maxcapacity=100;level=33;colorantname=magenta;", "index=5;class=supplyThatIsConsumed;type=ink;unit=percent;" "maxcapacity=100;level=67;colorantname=yellow;" }; static const char * const printer_supply_description[] = { // printer-supply-description values "Toner Waste Tank", "Black Toner" }; static const char * const printer_supply_description_color[] = { // printer-supply-description values "Ink Waste Tank", "Black Ink", "Cyan Ink", "Magenta Ink", "Yellow Ink" }; static const char * const pwg_raster_document_type_supported[] = { "black_1", "sgray_8" }; static const char * const pwg_raster_document_type_supported_color[] = { "black_1", "sgray_8", "srgb_8", "srgb_16" }; static const char * const sides_supported[] = { // sides-supported values "one-sided", "two-sided-long-edge", "two-sided-short-edge" }; // Open the PPD file... if ((ppd = ppdOpenFile(ppdfile)) == NULL) { ppd_status_t status; // Load error status = ppdLastError(&i); _cupsLangPrintf(stderr, _("ippeveprinter: Unable to open \"%s\": %s on line %d."), ppdfile, ppdErrorString(status), i); return (NULL); } ppdMarkDefaults(ppd); if ((pc = _ppdCacheCreateWithPPD(cupsLangDefault(), ppd)) == NULL) { _cupsLangPuts(stderr, _("ippeveprinter: Unable to create IPP mapping of PPD file.")); ppdClose(ppd); return (NULL); } if ((ppd_size = ppdPageSize(ppd, NULL)) != NULL) { // Look up default size... for (i = 0, pwg_size = pc->sizes; i < pc->num_sizes; i ++, pwg_size ++) { if (!strcmp(pwg_size->map.ppd, ppd_size->name)) { default_size = pwg_size; break; } } } if (!default_size) { // Default to A4 or Letter... for (i = 0, pwg_size = pc->sizes; i < pc->num_sizes; i ++, pwg_size ++) { if (!strcmp(pwg_size->map.ppd, "Letter") || !strcmp(pwg_size->map.ppd, "A4")) { default_size = pwg_size; break; } } if (!default_size) default_size = pc->sizes; // Last resort: first size } if ((ppd_choice = ppdFindMarkedChoice(ppd, "InputSlot")) != NULL) default_source = _ppdCacheGetSource(pc, ppd_choice->choice); if ((ppd_choice = ppdFindMarkedChoice(ppd, "MediaType")) != NULL) default_source = _ppdCacheGetType(pc, ppd_choice->choice); if ((ppd_attr = ppdFindAttr(ppd, "DefaultResolution", NULL)) != NULL) { // Use the PPD-defined default resolution... if ((i = sscanf(ppd_attr->value, "%dx%d", &xres, &yres)) == 1) yres = xres; else if (i < 0) xres = yres = 300; } else { // Use default of 300dpi... xres = yres = 300; } snprintf(urf_rs, sizeof(urf_rs), "RS%d", yres < xres ? yres : xres); num_urf = 0; urf[num_urf ++] = "V1.4"; urf[num_urf ++] = "CP1"; urf[num_urf ++] = urf_rs; urf[num_urf ++] = "W8"; if (pc->sides_2sided_long) urf[num_urf ++] = "DM1"; if (ppd->color_device) urf[num_urf ++] = "SRGB24"; // PostScript printers accept PDF via one of the CUPS PDF to PostScript // filters, along with PostScript (of course) and JPEG... cupsArrayAdd(docformats, "application/pdf"); cupsArrayAdd(docformats, "application/postscript"); cupsArrayAdd(docformats, "image/jpeg"); // Create the attributes... attrs = ippNew(); // color-supported ippAddBoolean(attrs, IPP_TAG_PRINTER, "color-supported", (char)ppd->color_device); // copies-default ippAddInteger(attrs, IPP_TAG_PRINTER, IPP_TAG_INTEGER, "copies-default", 1); // copies-supported ippAddRange(attrs, IPP_TAG_PRINTER, "copies-supported", 1, 999); // document-password-supported ippAddInteger(attrs, IPP_TAG_PRINTER, IPP_TAG_INTEGER, "document-password-supported", 127); // finishing-template-supported attr = ippAddStrings(attrs, IPP_TAG_PRINTER, IPP_TAG_KEYWORD, "finishing-template-supported", cupsArrayCount(pc->templates) + 1, NULL, NULL); ippSetString(attrs, &attr, 0, "none"); for (i = 1, template = (const char *)cupsArrayFirst(pc->templates); template; i ++, template = (const char *)cupsArrayNext(pc->templates)) ippSetString(attrs, &attr, i, template); // finishings-col-database attr = ippAddCollections(attrs, IPP_TAG_PRINTER, "finishings-col-database", cupsArrayCount(pc->templates) + 1, NULL); col = ippNew(); ippAddString(col, IPP_TAG_PRINTER, IPP_TAG_KEYWORD, "finishing-template", NULL, "none"); ippSetCollection(attrs, &attr, 0, col); ippDelete(col); for (i = 1, template = (const char *)cupsArrayFirst(pc->templates); template; i ++, template = (const char *)cupsArrayNext(pc->templates)) { col = ippNew(); ippAddString(col, IPP_TAG_PRINTER, IPP_TAG_KEYWORD, "finishing-template", NULL, template); ippSetCollection(attrs, &attr, i, col); ippDelete(col); } // finishings-col-default col = ippNew(); ippAddString(col, IPP_TAG_PRINTER, IPP_TAG_KEYWORD, "finishing-template", NULL, "none"); ippAddCollection(attrs, IPP_TAG_PRINTER, "finishings-col-default", col); ippDelete(col); // finishings-col-ready attr = ippAddCollections(attrs, IPP_TAG_PRINTER, "finishings-col-ready", cupsArrayCount(pc->templates) + 1, NULL); col = ippNew(); ippAddString(col, IPP_TAG_PRINTER, IPP_TAG_KEYWORD, "finishing-template", NULL, "none"); ippSetCollection(attrs, &attr, 0, col); ippDelete(col); for (i = 1, template = (const char *)cupsArrayFirst(pc->templates); template; i ++, template = (const char *)cupsArrayNext(pc->templates)) { col = ippNew(); ippAddString(col, IPP_TAG_PRINTER, IPP_TAG_KEYWORD, "finishing-template", NULL, template); ippSetCollection(attrs, &attr, i, col); ippDelete(col); } // finishings-col-supported ippAddString(attrs, IPP_TAG_PRINTER, IPP_CONST_TAG(IPP_TAG_KEYWORD), "finishings-col-supported", NULL, "finishing-template"); // finishings-default ippAddInteger(attrs, IPP_TAG_PRINTER, IPP_TAG_ENUM, "finishings-default", IPP_FINISHINGS_NONE); // finishings-ready attr = ippAddIntegers(attrs, IPP_TAG_PRINTER, IPP_TAG_ENUM, "finishings-ready", cupsArrayCount(pc->finishings) + 1, NULL); ippSetInteger(attrs, &attr, 0, IPP_FINISHINGS_NONE); for (i = 1, finishings = (_pwg_finishings_t *)cupsArrayFirst(pc->finishings); finishings; i ++, finishings = (_pwg_finishings_t *)cupsArrayNext(pc->finishings)) ippSetInteger(attrs, &attr, i, (int)finishings->value); // finishings-supported attr = ippAddIntegers(attrs, IPP_TAG_PRINTER, IPP_TAG_ENUM, "finishings-supported", cupsArrayCount(pc->finishings) + 1, NULL); ippSetInteger(attrs, &attr, 0, IPP_FINISHINGS_NONE); for (i = 1, finishings = (_pwg_finishings_t *)cupsArrayFirst(pc->finishings); finishings; i ++, finishings = (_pwg_finishings_t *)cupsArrayNext(pc->finishings)) ippSetInteger(attrs, &attr, i, (int)finishings->value); // media-bottom-margin-supported for (i = 0, num_margins = 0, pwg_size = pc->sizes; i < pc->num_sizes && num_margins < (int)(sizeof(margins) / sizeof(margins[0])); i ++, pwg_size ++) { for (j = 0; j < num_margins; j ++) { if (margins[j] == pwg_size->bottom) break; } if (j >= num_margins) margins[num_margins ++] = pwg_size->bottom; } for (i = 0; i < (num_margins - 1); i ++) { for (j = i + 1; j < num_margins; j ++) { if (margins[i] > margins[j]) { int mtemp = margins[i]; margins[i] = margins[j]; margins[j] = mtemp; } } } ippAddIntegers(attrs, IPP_TAG_PRINTER, IPP_TAG_INTEGER, "media-bottom-margin-supported", num_margins, margins); // media-col-database attr = ippAddCollections(attrs, IPP_TAG_PRINTER, "media-col-database", pc->num_sizes, NULL); for (i = 0, pwg_size = pc->sizes; i < pc->num_sizes; i ++, pwg_size ++) { size = create_media_size(pwg_size->width, pwg_size->length); col = create_media_col(pwg_size->map.pwg, /*source*/NULL, /*type*/NULL, size, pwg_size->bottom, pwg_size->left, pwg_size->right, pwg_size->top); ippSetCollection(attrs, &attr, i, col); ippDelete(col); ippDelete(size); } // media-col-default size = create_media_size(default_size->width, default_size->length); col = create_media_col(default_size->map.pwg, default_source, default_type, size, default_size->bottom, default_size->left, default_size->right, default_size->top); ippAddCollection(attrs, IPP_TAG_PRINTER, "media-col-default", col); ippDelete(col); ippDelete(size); // media-col-ready size = create_media_size(default_size->width, default_size->length); col = create_media_col(default_size->map.pwg, default_source, default_type, size, default_size->bottom, default_size->left, default_size->right, default_size->top); ippAddCollection(attrs, IPP_TAG_PRINTER, "media-col-ready", col); ippDelete(col); ippDelete(size); // media-default ippAddString(attrs, IPP_TAG_PRINTER, IPP_TAG_KEYWORD, "media-default", NULL, default_size->map.pwg); // media-left-margin-supported for (i = 0, num_margins = 0, pwg_size = pc->sizes; i < pc->num_sizes && num_margins < (int)(sizeof(margins) / sizeof(margins[0])); i ++, pwg_size ++) { for (j = 0; j < num_margins; j ++) { if (margins[j] == pwg_size->left) break; } if (j >= num_margins) margins[num_margins ++] = pwg_size->left; } for (i = 0; i < (num_margins - 1); i ++) { for (j = i + 1; j < num_margins; j ++) { if (margins[i] > margins[j]) { int mtemp = margins[i]; margins[i] = margins[j]; margins[j] = mtemp; } } } ippAddIntegers(attrs, IPP_TAG_PRINTER, IPP_TAG_INTEGER, "media-left-margin-supported", num_margins, margins); // media-ready ippAddString(attrs, IPP_TAG_PRINTER, IPP_TAG_KEYWORD, "media-ready", NULL, default_size->map.pwg); // media-right-margin-supported for (i = 0, num_margins = 0, pwg_size = pc->sizes; i < pc->num_sizes && num_margins < (int)(sizeof(margins) / sizeof(margins[0])); i ++, pwg_size ++) { for (j = 0; j < num_margins; j ++) { if (margins[j] == pwg_size->right) break; } if (j >= num_margins) margins[num_margins ++] = pwg_size->right; } for (i = 0; i < (num_margins - 1); i ++) { for (j = i + 1; j < num_margins; j ++) { if (margins[i] > margins[j]) { int mtemp = margins[i]; margins[i] = margins[j]; margins[j] = mtemp; } } } ippAddIntegers(attrs, IPP_TAG_PRINTER, IPP_TAG_INTEGER, "media-right-margin-supported", num_margins, margins); // media-supported attr = ippAddStrings(attrs, IPP_TAG_PRINTER, IPP_TAG_KEYWORD, "media-supported", pc->num_sizes, NULL, NULL); for (i = 0, pwg_size = pc->sizes; i < pc->num_sizes; i ++, pwg_size ++) ippSetString(attrs, &attr, i, pwg_size->map.pwg); // media-size-supported attr = ippAddCollections(attrs, IPP_TAG_PRINTER, "media-size-supported", pc->num_sizes, NULL); for (i = 0, pwg_size = pc->sizes; i < pc->num_sizes; i ++, pwg_size ++) { col = create_media_size(pwg_size->width, pwg_size->length); ippSetCollection(attrs, &attr, i, col); ippDelete(col); } // media-source-supported if (pc->num_sources > 0) { attr = ippAddStrings(attrs, IPP_TAG_PRINTER, IPP_TAG_KEYWORD, "media-source-supported", pc->num_sources, NULL, NULL); for (i = 0, pwg_map = pc->sources; i < pc->num_sources; i ++, pwg_map ++) ippSetString(attrs, &attr, i, pwg_map->pwg); } else { ippAddString(attrs, IPP_TAG_PRINTER, IPP_CONST_TAG(IPP_TAG_KEYWORD), "media-source-supported", NULL, "auto"); } // media-top-margin-supported for (i = 0, num_margins = 0, pwg_size = pc->sizes; i < pc->num_sizes && num_margins < (int)(sizeof(margins) / sizeof(margins[0])); i ++, pwg_size ++) { for (j = 0; j < num_margins; j ++) { if (margins[j] == pwg_size->top) break; } if (j >= num_margins) margins[num_margins ++] = pwg_size->top; } for (i = 0; i < (num_margins - 1); i ++) { for (j = i + 1; j < num_margins; j ++) { if (margins[i] > margins[j]) { int mtemp = margins[i]; margins[i] = margins[j]; margins[j] = mtemp; } } } ippAddIntegers(attrs, IPP_TAG_PRINTER, IPP_TAG_INTEGER, "media-top-margin-supported", num_margins, margins); // media-type-supported if (pc->num_types > 0) { attr = ippAddStrings(attrs, IPP_TAG_PRINTER, IPP_TAG_KEYWORD, "media-type-supported", pc->num_types, NULL, NULL); for (i = 0, pwg_map = pc->types; i < pc->num_types; i ++, pwg_map ++) ippSetString(attrs, &attr, i, pwg_map->pwg); } else { ippAddString(attrs, IPP_TAG_PRINTER, IPP_CONST_TAG(IPP_TAG_KEYWORD), "media-type-supported", NULL, "auto"); } // orientation-requested-default ippAddInteger(attrs, IPP_TAG_PRINTER, IPP_TAG_ENUM, "orientation-requested-default", IPP_ORIENT_PORTRAIT); // orientation-requested-supported ippAddIntegers(attrs, IPP_TAG_PRINTER, IPP_TAG_ENUM, "orientation-requested-supported", (int)(sizeof(orientation_requested_supported) / sizeof(orientation_requested_supported[0])), orientation_requested_supported); // output-bin-default if (pc->num_bins > 0) ippAddString(attrs, IPP_TAG_PRINTER, IPP_TAG_KEYWORD, "output-bin-default", NULL, pc->bins->pwg); else ippAddString(attrs, IPP_TAG_PRINTER, IPP_CONST_TAG(IPP_TAG_KEYWORD), "output-bin-default", NULL, "face-down"); // output-bin-supported if (pc->num_bins > 0) { attr = ippAddStrings(attrs, IPP_TAG_PRINTER, IPP_TAG_KEYWORD, "output-bin-supported", pc->num_bins, NULL, NULL); for (i = 0, pwg_map = pc->bins; i < pc->num_bins; i ++, pwg_map ++) ippSetString(attrs, &attr, i, pwg_map->pwg); } else { ippAddString(attrs, IPP_TAG_PRINTER, IPP_CONST_TAG(IPP_TAG_KEYWORD), "output-bin-supported", NULL, "face-down"); } // overrides-supported ippAddStrings(attrs, IPP_TAG_PRINTER, IPP_CONST_TAG(IPP_TAG_KEYWORD), "overrides-supported", (int)(sizeof(overrides_supported) / sizeof(overrides_supported[0])), NULL, overrides_supported); // page-ranges-supported ippAddBoolean(attrs, IPP_TAG_PRINTER, "page-ranges-supported", 1); // pages-per-minute ippAddInteger(attrs, IPP_TAG_PRINTER, IPP_TAG_INTEGER, "pages-per-minute", ppd->throughput); // pages-per-minute-color if (ppd->color_device) ippAddInteger(attrs, IPP_TAG_PRINTER, IPP_TAG_INTEGER, "pages-per-minute-color", ppd->throughput); // print-color-mode-default ippAddString(attrs, IPP_TAG_PRINTER, IPP_CONST_TAG(IPP_TAG_KEYWORD), "print-color-mode-default", NULL, ppd->color_device ? "auto" : "monochrome"); // print-color-mode-supported if (ppd->color_device) ippAddStrings(attrs, IPP_TAG_PRINTER, IPP_CONST_TAG(IPP_TAG_KEYWORD), "print-color-mode-supported", (int)(sizeof(print_color_mode_supported_color) / sizeof(print_color_mode_supported_color[0])), NULL, print_color_mode_supported_color); else ippAddStrings(attrs, IPP_TAG_PRINTER, IPP_CONST_TAG(IPP_TAG_KEYWORD), "print-color-mode-supported", (int)(sizeof(print_color_mode_supported) / sizeof(print_color_mode_supported[0])), NULL, print_color_mode_supported); // print-content-optimize-default ippAddString(attrs, IPP_TAG_PRINTER, IPP_CONST_TAG(IPP_TAG_KEYWORD), "print-content-optimize-default", NULL, "auto"); // print-content-optimize-supported ippAddString(attrs, IPP_TAG_PRINTER, IPP_CONST_TAG(IPP_TAG_KEYWORD), "print-content-optimize-supported", NULL, "auto"); // print-quality-default ippAddInteger(attrs, IPP_TAG_PRINTER, IPP_TAG_ENUM, "print-quality-default", IPP_QUALITY_NORMAL); // print-quality-supported ippAddIntegers(attrs, IPP_TAG_PRINTER, IPP_TAG_ENUM, "print-quality-supported", (int)(sizeof(print_quality_supported) / sizeof(print_quality_supported[0])), print_quality_supported); // print-rendering-intent-default ippAddString(attrs, IPP_TAG_PRINTER, IPP_CONST_TAG(IPP_TAG_KEYWORD), "print-rendering-intent-default", NULL, "auto"); // print-rendering-intent-supported ippAddString(attrs, IPP_TAG_PRINTER, IPP_CONST_TAG(IPP_TAG_KEYWORD), "print-rendering-intent-supported", NULL, "auto"); // printer-device-id if ((ppd_attr = ppdFindAttr(ppd, "1284DeviceId", NULL)) != NULL) { // Use the device ID string from the PPD... ippAddString(attrs, IPP_TAG_PRINTER, IPP_TAG_TEXT, "printer-device-id", NULL, ppd_attr->value); } else { // Synthesize a device ID string... char device_id[1024]; // Device ID string snprintf(device_id, sizeof(device_id), "MFG:%s;MDL:%s;CMD:PS;", ppd->manufacturer, ppd->modelname); ippAddString(attrs, IPP_TAG_PRINTER, IPP_TAG_TEXT, "printer-device-id", NULL, device_id); } // printer-input-tray if (pc->num_sources > 0) { for (i = 0, attr = NULL; i < pc->num_sources; i ++) { char input_tray[1024]; // printer-input-tray value if (!strcmp(pc->sources[i].pwg, "manual") || strstr(pc->sources[i].pwg, "-man") != NULL) snprintf(input_tray, sizeof(input_tray), "type=sheetFeedManual;mediafeed=0;mediaxfeed=0;maxcapacity=1;level=-2;status=0;name=%s", pc->sources[i].pwg); else snprintf(input_tray, sizeof(input_tray), "type=sheetFeedAutoRemovableTray;mediafeed=0;mediaxfeed=0;maxcapacity=250;level=125;status=0;name=%s", pc->sources[i].pwg); if (attr) ippSetOctetString(attrs, &attr, i, input_tray, (int)strlen(input_tray)); else attr = ippAddOctetString(attrs, IPP_TAG_PRINTER, "printer-input-tray", input_tray, (int)strlen(input_tray)); } } else { static const char *printer_input_tray = "type=sheetFeedAutoRemovableTray;mediafeed=0;mediaxfeed=0;maxcapacity=-2;level=-2;status=0;name=auto"; ippAddOctetString(attrs, IPP_TAG_PRINTER, "printer-input-tray", printer_input_tray, (int)strlen(printer_input_tray)); } // printer-make-and-model ippAddString(attrs, IPP_TAG_PRINTER, IPP_TAG_TEXT, "printer-make-and-model", NULL, ppd->nickname); // printer-resolution-default ippAddResolution(attrs, IPP_TAG_PRINTER, "printer-resolution-default", IPP_RES_PER_INCH, xres, yres); // printer-resolution-supported ippAddResolution(attrs, IPP_TAG_PRINTER, "printer-resolution-supported", IPP_RES_PER_INCH, xres, yres); // printer-supply and printer-supply-description if (ppd->color_device) { attr = ippAddOctetString(attrs, IPP_TAG_PRINTER, "printer-supply", printer_supply_color[0], (int)strlen(printer_supply_color[0])); for (i = 1; i < (int)(sizeof(printer_supply_color) / sizeof(printer_supply_color[0])); i ++) ippSetOctetString(attrs, &attr, i, printer_supply_color[i], (int)strlen(printer_supply_color[i])); ippAddStrings(attrs, IPP_TAG_PRINTER, IPP_CONST_TAG(IPP_TAG_TEXT), "printer-supply-description", (int)(sizeof(printer_supply_description_color) / sizeof(printer_supply_description_color[0])), NULL, printer_supply_description_color); } else { attr = ippAddOctetString(attrs, IPP_TAG_PRINTER, "printer-supply", printer_supply[0], (int)strlen(printer_supply[0])); for (i = 1; i < (int)(sizeof(printer_supply) / sizeof(printer_supply[0])); i ++) ippSetOctetString(attrs, &attr, i, printer_supply[i], (int)strlen(printer_supply[i])); ippAddStrings(attrs, IPP_TAG_PRINTER, IPP_CONST_TAG(IPP_TAG_TEXT), "printer-supply-description", (int)(sizeof(printer_supply_description) / sizeof(printer_supply_description[0])), NULL, printer_supply_description); } // pwg-raster-document-xxx-supported if (cupsArrayFind(docformats, (void *)"image/pwg-raster")) { ippAddResolution(attrs, IPP_TAG_PRINTER, "pwg-raster-document-resolution-supported", IPP_RES_PER_INCH, xres, yres); if (pc->sides_2sided_long) ippAddString(attrs, IPP_TAG_PRINTER, IPP_CONST_TAG(IPP_TAG_KEYWORD), "pwg-raster-document-sheet-back", NULL, "normal"); if (ppd->color_device) ippAddStrings(attrs, IPP_TAG_PRINTER, IPP_CONST_TAG(IPP_TAG_KEYWORD), "pwg-raster-document-type-supported", (int)(sizeof(pwg_raster_document_type_supported_color) / sizeof(pwg_raster_document_type_supported_color[0])), NULL, pwg_raster_document_type_supported_color); else ippAddStrings(attrs, IPP_TAG_PRINTER, IPP_CONST_TAG(IPP_TAG_KEYWORD), "pwg-raster-document-type-supported", (int)(sizeof(pwg_raster_document_type_supported) / sizeof(pwg_raster_document_type_supported[0])), NULL, pwg_raster_document_type_supported); } // sides-default ippAddString(attrs, IPP_TAG_PRINTER, IPP_CONST_TAG(IPP_TAG_KEYWORD), "sides-default", NULL, "one-sided"); // sides-supported if (pc->sides_2sided_long) ippAddStrings(attrs, IPP_TAG_PRINTER, IPP_CONST_TAG(IPP_TAG_KEYWORD), "sides-supported", (int)(sizeof(sides_supported) / sizeof(sides_supported[0])), NULL, sides_supported); else ippAddString(attrs, IPP_TAG_PRINTER, IPP_CONST_TAG(IPP_TAG_KEYWORD), "sides-supported", NULL, "one-sided"); // urf-supported if (cupsArrayFind(docformats, (void *)"image/urf")) ippAddStrings(attrs, IPP_TAG_PRINTER, IPP_TAG_KEYWORD, "urf-supported", num_urf, NULL, urf); // Free the PPD file and return the attributes... _ppdCacheDestroy(pc); ppdClose(ppd); return (attrs); } #endif // !CUPS_LITE #if HAVE_LIBPAM // // 'pam_func()' - PAM conversation function. // static int // O - Success or failure pam_func( int num_msg, // I - Number of messages const struct pam_message **msg, // I - Messages struct pam_response **resp, // O - Responses void *appdata_ptr) // I - Pointer to connection { int i; // Looping var struct pam_response *replies; // Replies ippeve_authdata_t *data; // Pointer to auth data // Allocate memory for the responses... if ((replies = malloc(sizeof(struct pam_response) * (size_t)num_msg)) == NULL) return (PAM_CONV_ERR); // Answer all of the messages... data = (ippeve_authdata_t *)appdata_ptr; for (i = 0; i < num_msg; i ++) { switch (msg[i]->msg_style) { case PAM_PROMPT_ECHO_ON: replies[i].resp_retcode = PAM_SUCCESS; replies[i].resp = strdup(data->username); break; case PAM_PROMPT_ECHO_OFF: replies[i].resp_retcode = PAM_SUCCESS; replies[i].resp = strdup(data->password); break; case PAM_TEXT_INFO: replies[i].resp_retcode = PAM_SUCCESS; replies[i].resp = NULL; break; case PAM_ERROR_MSG: replies[i].resp_retcode = PAM_SUCCESS; replies[i].resp = NULL; break; default: free(replies); return (PAM_CONV_ERR); } } // Return the responses back to PAM... *resp = replies; return (PAM_SUCCESS); } #endif // HAVE_LIBPAM // // 'parse_options()' - Parse URL options into CUPS options. // // The client->options string is destroyed by this function. // static int // O - Number of options parse_options(ippeve_client_t *client, // I - Client cups_option_t **options)// O - Options { char *name, // Name *value, // Value *next; // Next name=value pair int num_options = 0; // Number of options *options = NULL; for (name = client->options; name && *name; name = next) { if ((value = strchr(name, '=')) == NULL) break; *value++ = '\0'; if ((next = strchr(value, '&')) != NULL) *next++ = '\0'; num_options = cupsAddOption(name, value, num_options, options); } return (num_options); } // // 'process_attr_message()' - Process an ATTR: message from a command. // static void process_attr_message( ippeve_job_t *job, // I - Job char *message) // I - Message { int i, // Looping var num_options = 0; // Number of name=value pairs cups_option_t *options = NULL, // name=value pairs from message *option; // Current option ipp_attribute_t *attr; // Current attribute // Grab attributes from the message line... num_options = cupsParseOptions(message + 5, num_options, &options); // Loop through the options and record them in the printer or job objects... for (i = num_options, option = options; i > 0; i --, option ++) { if (!strcmp(option->name, "job-impressions")) { // Update job-impressions attribute... job->impressions = atoi(option->value); } else if (!strcmp(option->name, "job-impressions-completed")) { // Update job-impressions-completed attribute... job->impcompleted = atoi(option->value); } else if (!strncmp(option->name, "marker-", 7) || !strcmp(option->name, "printer-alert") || !strcmp(option->name, "printer-alert-description") || !strcmp(option->name, "printer-supply") || !strcmp(option->name, "printer-supply-description")) { // Update Printer Status attribute... cupsRWLockWrite(&job->printer->rwlock); if ((attr = ippFindAttribute(job->printer->attrs, option->name, IPP_TAG_ZERO)) != NULL) ippDeleteAttribute(job->printer->attrs, attr); cupsEncodeOption(job->printer->attrs, IPP_TAG_PRINTER, option->name, option->value); cupsRWUnlock(&job->printer->rwlock); } else { // Something else that isn't currently supported... fprintf(stderr, "[Job %d] Ignoring update of attribute \"%s\" with value \"%s\".\n", job->id, option->name, option->value); } } cupsFreeOptions(num_options, options); } // // 'process_client()' - Process client requests on a thread. // static void * // O - Exit status process_client(ippeve_client_t *client) // I - Client { // Loop until we are out of requests or timeout (30 seconds)... bool first_time = true; // First time request? while (httpWait(client->http, 30000)) { if (first_time) { // See if we need to negotiate a TLS connection... char buf[1]; // First byte from client if (recv(httpGetFd(client->http), buf, 1, MSG_PEEK) == 1 && (!buf[0] || !strchr("DGHOPT", buf[0]))) { char security[256]; // Security description fprintf(stderr, "%s Starting HTTPS session.\n", client->hostname); if (!httpSetEncryption(client->http, HTTP_ENCRYPTION_ALWAYS)) { fprintf(stderr, "%s Unable to encrypt connection: %s\n", client->hostname, cupsGetErrorString()); break; } fprintf(stderr, "%s Connection now encrypted (%s).\n", client->hostname, httpGetSecurity(client->http, security, sizeof(security))); } first_time = false; } if (!process_http(client)) break; } // Close the conection to the client and return... delete_client(client); return (NULL); } // // 'process_http()' - Process a HTTP request. // int // O - 1 on success, 0 on failure process_http(ippeve_client_t *client) // I - Client connection { char uri[1024]; // URI http_state_t http_state; // HTTP state http_status_t http_status; // HTTP status ipp_state_t ipp_state; // State of IPP transfer char scheme[32], // Method/scheme userpass[128], // Username:password hostname[HTTP_MAX_HOST], // Hostname *ptr; // Pointer into value int port; // Port number // Clear state variables... client->username[0] = '\0'; ippDelete(client->request); ippDelete(client->response); client->request = NULL; client->response = NULL; client->operation = HTTP_STATE_WAITING; // Read a request from the connection... while ((http_state = httpReadRequest(client->http, uri, sizeof(uri))) == HTTP_STATE_WAITING) usleep(1); // Parse the request line... if (http_state == HTTP_STATE_ERROR) { if (httpGetError(client->http) == EPIPE) fprintf(stderr, "%s Client closed connection.\n", client->hostname); else fprintf(stderr, "%s Bad request line (%s).\n", client->hostname, strerror(httpGetError(client->http))); return (0); } else if (http_state == HTTP_STATE_UNKNOWN_METHOD) { fprintf(stderr, "%s Bad/unknown operation.\n", client->hostname); respond_http(client, HTTP_STATUS_BAD_REQUEST, NULL, NULL, 0); return (0); } else if (http_state == HTTP_STATE_UNKNOWN_VERSION) { fprintf(stderr, "%s Bad HTTP version.\n", client->hostname); respond_http(client, HTTP_STATUS_BAD_REQUEST, NULL, NULL, 0); return (0); } fprintf(stderr, "%s %s %s\n", client->hostname, httpStateString(http_state), uri); // Separate the URI into its components... if (httpSeparateURI(HTTP_URI_CODING_MOST, uri, scheme, sizeof(scheme), userpass, sizeof(userpass), hostname, sizeof(hostname), &port, client->uri, sizeof(client->uri)) < HTTP_URI_STATUS_OK && (http_state != HTTP_STATE_OPTIONS || strcmp(uri, "*"))) { fprintf(stderr, "%s Bad URI \"%s\".\n", client->hostname, uri); respond_http(client, HTTP_STATUS_BAD_REQUEST, NULL, NULL, 0); return (0); } if ((client->options = strchr(client->uri, '?')) != NULL) *(client->options)++ = '\0'; // Process the request... client->start = time(NULL); client->operation = httpGetState(client->http); // Parse incoming parameters until the status changes... while ((http_status = httpUpdate(client->http)) == HTTP_STATUS_CONTINUE); if (http_status != HTTP_STATUS_OK) { respond_http(client, HTTP_STATUS_BAD_REQUEST, NULL, NULL, 0); return (0); } // Validate the host header... if (!httpGetField(client->http, HTTP_FIELD_HOST)[0] && httpGetVersion(client->http) >= HTTP_VERSION_1_1) { // HTTP/1.1 and higher require the "Host:" field... fprintf(stderr, "%s Missing Host: header.\n", client->hostname); respond_http(client, HTTP_STATUS_BAD_REQUEST, NULL, NULL, 0); return (0); } cupsCopyString(client->host_field, httpGetField(client->http, HTTP_FIELD_HOST), sizeof(client->host_field)); if ((ptr = strrchr(client->host_field, ':')) != NULL) { // Grab port number from Host: header... *ptr++ = '\0'; client->host_port = atoi(ptr); } else { // Use the default port number... client->host_port = client->printer->port; } if ((ptr = strstr(client->host_field, ".local")) == NULL) ptr = strrchr(client->host_field, '.'); if (!_cups_isdigit(client->host_field[0]) && client->host_field[0] != '[' && strcmp(client->host_field, client->printer->hostname) && strcmp(client->host_field, "localhost") && (!ptr || (strcmp(ptr, ".local") && strcmp(ptr, ".local.")))) { fprintf(stderr, "%s Bad Host: header '%s'.\n", client->hostname, client->host_field); respond_http(client, HTTP_STATUS_BAD_REQUEST, NULL, NULL, 0); return (0); } // Handle HTTP Upgrade... if (!strcasecmp(httpGetField(client->http, HTTP_FIELD_CONNECTION), "Upgrade")) { if (strstr(httpGetField(client->http, HTTP_FIELD_UPGRADE), "TLS/") != NULL && !httpIsEncrypted(client->http)) { char security[256]; // Security description if (!respond_http(client, HTTP_STATUS_SWITCHING_PROTOCOLS, NULL, NULL, 0)) return (0); fprintf(stderr, "%s Upgrading to encrypted connection.\n", client->hostname); if (!httpSetEncryption(client->http, HTTP_ENCRYPTION_REQUIRED)) { fprintf(stderr, "%s Unable to encrypt connection: %s\n", client->hostname, cupsGetErrorString()); return (0); } fprintf(stderr, "%s Connection now encrypted (%s).\n", client->hostname, httpGetSecurity(client->http, security, sizeof(security))); } else if (!respond_http(client, HTTP_STATUS_NOT_IMPLEMENTED, NULL, NULL, 0)) return (0); } // Handle new transfers... switch (client->operation) { case HTTP_STATE_OPTIONS : // Do OPTIONS command... return (respond_http(client, HTTP_STATUS_OK, NULL, NULL, 0)); case HTTP_STATE_HEAD : if (!strcmp(client->uri, "/en.strings")) return (respond_http(client, HTTP_STATUS_OK, NULL, "text/strings", 0)); else if (!strcmp(client->uri, "/icon.png") || !strcmp(client->uri, "/icon-lg.png") || !strcmp(client->uri, "/icon-sm.png")) return (respond_http(client, HTTP_STATUS_OK, NULL, "image/png", 0)); else if (!strcmp(client->uri, "/") || !strcmp(client->uri, "/media") || !strcmp(client->uri, "/supplies")) return (respond_http(client, HTTP_STATUS_OK, NULL, "text/html", 0)); else return (respond_http(client, HTTP_STATUS_NOT_FOUND, NULL, NULL, 0)); case HTTP_STATE_GET : if (!strcmp(client->uri, "/en.strings")) { // Send strings file. if (client->printer->strings) { int fd; // Icon file struct stat fileinfo; // Icon file information char buffer[4096]; // Copy buffer ssize_t bytes; // Bytes if ((fd = open(client->printer->strings, O_RDONLY | O_BINARY)) >= 0) { if (fstat(fd, &fileinfo)) { close(fd); return (0); } else if (!respond_http(client, HTTP_STATUS_OK, NULL, "text/strings", (size_t)fileinfo.st_size)) { close(fd); return (0); } while ((bytes = read(fd, buffer, sizeof(buffer))) > 0) httpWrite(client->http, buffer, (size_t)bytes); httpFlushWrite(client->http); close(fd); } else return (respond_http(client, HTTP_STATUS_NOT_FOUND, NULL, NULL, 0)); } else return (respond_http(client, HTTP_STATUS_NOT_FOUND, NULL, NULL, 0)); } else if (!strcmp(client->uri, "/icon.png")) { // Send medium PNG icon file. if (client->printer->icons[1]) { int fd; // Icon file struct stat fileinfo; // Icon file information char buffer[4096]; // Copy buffer ssize_t bytes; // Bytes if ((fd = open(client->printer->icons[1], O_RDONLY | O_BINARY)) >= 0) { if (fstat(fd, &fileinfo)) { close(fd); return (0); } else if (!respond_http(client, HTTP_STATUS_OK, NULL, "image/png", (size_t)fileinfo.st_size)) { close(fd); return (0); } while ((bytes = read(fd, buffer, sizeof(buffer))) > 0) httpWrite(client->http, buffer, (size_t)bytes); httpFlushWrite(client->http); close(fd); } else return (respond_http(client, HTTP_STATUS_NOT_FOUND, NULL, NULL, 0)); } else { fputs("Icon file is internal printer.png.\n", stderr); if (!respond_http(client, HTTP_STATUS_OK, NULL, "image/png", sizeof(printer_png))) return (0); httpWrite(client->http, (const char *)printer_png, sizeof(printer_png)); httpFlushWrite(client->http); } } else if (!strcmp(client->uri, "/icon-lg.png")) { // Send large PNG icon file. if (client->printer->icons[2]) { int fd; // Icon file struct stat fileinfo; // Icon file information char buffer[4096]; // Copy buffer ssize_t bytes; // Bytes if ((fd = open(client->printer->icons[2], O_RDONLY | O_BINARY)) >= 0) { if (fstat(fd, &fileinfo)) { close(fd); return (0); } else if (!respond_http(client, HTTP_STATUS_OK, NULL, "image/png", (size_t)fileinfo.st_size)) { close(fd); return (0); } while ((bytes = read(fd, buffer, sizeof(buffer))) > 0) httpWrite(client->http, buffer, (size_t)bytes); httpFlushWrite(client->http); close(fd); } else return (respond_http(client, HTTP_STATUS_NOT_FOUND, NULL, NULL, 0)); } else { fputs("Icon file is internal printer-lg.png.\n", stderr); if (!respond_http(client, HTTP_STATUS_OK, NULL, "image/png", sizeof(printer_lg_png))) return (0); httpWrite(client->http, (const char *)printer_lg_png, sizeof(printer_lg_png)); httpFlushWrite(client->http); } } else if (!strcmp(client->uri, "/icon-sm.png")) { // Send small PNG icon file. if (client->printer->icons[0]) { int fd; // Icon file struct stat fileinfo; // Icon file information char buffer[4096]; // Copy buffer ssize_t bytes; // Bytes if ((fd = open(client->printer->icons[0], O_RDONLY | O_BINARY)) >= 0) { if (fstat(fd, &fileinfo)) { close(fd); return (0); } else if (!respond_http(client, HTTP_STATUS_OK, NULL, "image/png", (size_t)fileinfo.st_size)) { close(fd); return (0); } while ((bytes = read(fd, buffer, sizeof(buffer))) > 0) httpWrite(client->http, buffer, (size_t)bytes); httpFlushWrite(client->http); close(fd); } else return (respond_http(client, HTTP_STATUS_NOT_FOUND, NULL, NULL, 0)); } else { fputs("Icon file is internal printer-sm.png.\n", stderr); if (!respond_http(client, HTTP_STATUS_OK, NULL, "image/png", sizeof(printer_sm_png))) return (0); httpWrite(client->http, (const char *)printer_sm_png, sizeof(printer_sm_png)); httpFlushWrite(client->http); } } else { // Authenticate if needed... if ((http_status = authenticate_request(client)) != HTTP_STATUS_CONTINUE) { return (respond_http(client, http_status, NULL, NULL, 0)); } if (!strcmp(client->uri, "/")) { // Show web status page... return (show_status(client)); } else if (!strcmp(client->uri, "/media")) { // Show web media page... return (show_media(client)); } else if (!strcmp(client->uri, "/supplies")) { // Show web supplies page... return (show_supplies(client)); } else return (respond_http(client, HTTP_STATUS_NOT_FOUND, NULL, NULL, 0)); } break; case HTTP_STATE_POST : if (strcmp(httpGetField(client->http, HTTP_FIELD_CONTENT_TYPE), "application/ipp")) { // Not an IPP request... return (respond_http(client, HTTP_STATUS_BAD_REQUEST, NULL, NULL, 0)); } // Read the IPP request... client->request = ippNew(); while ((ipp_state = ippRead(client->http, client->request)) != IPP_STATE_DATA) { if (ipp_state == IPP_STATE_ERROR) { fprintf(stderr, "%s IPP read error (%s).\n", client->hostname, cupsGetErrorString()); respond_http(client, HTTP_STATUS_BAD_REQUEST, NULL, NULL, 0); return (0); } } // Now that we have the IPP request, process the request... return (process_ipp(client)); default : break; // Anti-compiler-warning-code } return (1); } // // 'process_ipp()' - Process an IPP request. // static int // O - 1 on success, 0 on error process_ipp(ippeve_client_t *client) // I - Client { ipp_tag_t group; // Current group tag ipp_attribute_t *attr; // Current attribute ipp_attribute_t *charset; // Character set attribute ipp_attribute_t *language; // Language attribute ipp_attribute_t *uri; // Printer URI attribute int major, minor; // Version number const char *name; // Name of attribute http_status_t status; // Authentication status debug_attributes("Request", client->request, 1); // First build an empty response message for this request... client->operation_id = ippGetOperation(client->request); client->response = ippNewResponse(client->request); // Then validate the request header and required attributes... major = ippGetVersion(client->request, &minor); if (major < 1 || major > 2) { // Return an error, since we only support IPP 1.x and 2.x. respond_ipp(client, IPP_STATUS_ERROR_VERSION_NOT_SUPPORTED, "Bad request version number %d.%d.", major, minor); } else if ((major * 10 + minor) > MaxVersion) { if (httpGetState(client->http) != HTTP_STATE_POST_SEND) httpFlush(client->http); // Flush trailing (junk) data respond_http(client, HTTP_STATUS_BAD_REQUEST, NULL, NULL, 0); return (0); } else if (ippGetRequestId(client->request) <= 0) { respond_ipp(client, IPP_STATUS_ERROR_BAD_REQUEST, "Bad request-id %d.", ippGetRequestId(client->request)); } else if (!ippGetFirstAttribute(client->request)) { respond_ipp(client, IPP_STATUS_ERROR_BAD_REQUEST, "No attributes in request."); } else { // Make sure that the attributes are provided in the correct order and don't repeat groups... for (attr = ippGetFirstAttribute(client->request), group = ippGetGroupTag(attr); attr; attr = ippGetNextAttribute(client->request)) { if (ippGetGroupTag(attr) < group && ippGetGroupTag(attr) != IPP_TAG_ZERO) { // Out of order; return an error... respond_ipp(client, IPP_STATUS_ERROR_BAD_REQUEST, "Attribute groups are out of order (%x < %x).", ippGetGroupTag(attr), group); break; } else { group = ippGetGroupTag(attr); } } if (!attr) { // Then make sure that the first three attributes are: // // attributes-charset // attributes-natural-language // printer-uri/job-uri attr = ippGetFirstAttribute(client->request); name = ippGetName(attr); if (attr && name && !strcmp(name, "attributes-charset") && ippGetValueTag(attr) == IPP_TAG_CHARSET) charset = attr; else charset = NULL; attr = ippGetNextAttribute(client->request); name = ippGetName(attr); if (attr && name && !strcmp(name, "attributes-natural-language") && ippGetValueTag(attr) == IPP_TAG_LANGUAGE) language = attr; else language = NULL; if ((attr = ippFindAttribute(client->request, "printer-uri", IPP_TAG_URI)) != NULL) uri = attr; else if ((attr = ippFindAttribute(client->request, "job-uri", IPP_TAG_URI)) != NULL) uri = attr; else uri = NULL; if (charset && strcasecmp(ippGetString(charset, 0, NULL), "us-ascii") && strcasecmp(ippGetString(charset, 0, NULL), "utf-8")) { // Bad character set... respond_ipp(client, IPP_STATUS_ERROR_CHARSET, "Unsupported character set '%s'.", ippGetString(charset, 0, NULL)); attr = ippCopyAttribute(client->response, charset, 0); ippSetGroupTag(client->response, &attr, IPP_TAG_UNSUPPORTED_GROUP); } else if (!charset || !language || !uri) { // Return an error, since attributes-charset, // attributes-natural-language, and printer-uri/job-uri are required // for all operations. respond_ipp(client, IPP_STATUS_ERROR_BAD_REQUEST, "Missing required attributes."); } else { char scheme[32], // URI scheme userpass[32], // Username/password in URI host[256], // Host name in URI resource[256]; // Resource path in URI int port; // Port number in URI name = ippGetName(uri); if (httpSeparateURI(HTTP_URI_CODING_ALL, ippGetString(uri, 0, NULL), scheme, sizeof(scheme), userpass, sizeof(userpass), host, sizeof(host), &port, resource, sizeof(resource)) < HTTP_URI_STATUS_OK) { respond_ipp(client, IPP_STATUS_ERROR_ATTRIBUTES_OR_VALUES, "Bad %s value '%s'.", name, ippGetString(uri, 0, NULL)); } else if ((!strcmp(name, "job-uri") && strncmp(resource, "/ipp/print/", 11)) || (!strcmp(name, "printer-uri") && strcmp(resource, "/ipp/print") && (strcmp(resource, "/") || ippGetOperation(client->request) != IPP_OP_GET_PRINTER_ATTRIBUTES))) { respond_ipp(client, IPP_STATUS_ERROR_NOT_FOUND, "%s %s not found.", name, ippGetString(uri, 0, NULL)); } else if (client->operation_id != IPP_OP_GET_PRINTER_ATTRIBUTES && (status = authenticate_request(client)) != HTTP_STATUS_CONTINUE) { flush_document_data(client); return (respond_http(client, status, NULL, NULL, 0)); } else { // Handle HTTP Expect... if (httpGetExpect(client->http)) { if (httpGetExpect(client->http) == HTTP_STATUS_CONTINUE) { // Send 100-continue header... if (!respond_http(client, HTTP_STATUS_CONTINUE, NULL, NULL, 0)) return (0); } else { // Send 417-expectation-failed header... if (!respond_http(client, HTTP_STATUS_EXPECTATION_FAILED, NULL, NULL, 0)) return (0); flush_document_data(client); return (1); } } // Try processing the operation... switch (client->operation_id) { case IPP_OP_PRINT_JOB : ipp_print_job(client); break; case IPP_OP_PRINT_URI : ipp_print_uri(client); break; case IPP_OP_VALIDATE_JOB : ipp_validate_job(client); break; case IPP_OP_CREATE_JOB : ipp_create_job(client); break; case IPP_OP_SEND_DOCUMENT : ipp_send_document(client); break; case IPP_OP_SEND_URI : ipp_send_uri(client); break; case IPP_OP_CANCEL_JOB : ipp_cancel_job(client); break; case IPP_OP_CANCEL_JOBS : case IPP_OP_CANCEL_MY_JOBS : ipp_cancel_jobs(client); break; case IPP_OP_GET_JOB_ATTRIBUTES : ipp_get_job_attributes(client); break; case IPP_OP_GET_JOBS : ipp_get_jobs(client); break; case IPP_OP_GET_PRINTER_ATTRIBUTES : ipp_get_printer_attributes(client); break; case IPP_OP_CLOSE_JOB : ipp_close_job(client); break; case IPP_OP_IDENTIFY_PRINTER : ipp_identify_printer(client); break; default : respond_ipp(client, IPP_STATUS_ERROR_OPERATION_NOT_SUPPORTED, "Operation not supported."); break; } } } } } // Send the HTTP header and return... if (httpGetState(client->http) != HTTP_STATE_POST_SEND) httpFlush(client->http); // Flush trailing (junk) data return (respond_http(client, HTTP_STATUS_OK, NULL, "application/ipp", ippGetLength(client->response))); } // // 'process_job()' - Process a print job. // static void * // O - Thread exit status process_job(ippeve_job_t *job) // I - Job { job->state = IPP_JSTATE_PROCESSING; job->printer->state = IPP_PSTATE_PROCESSING; job->processing = time(NULL); while (job->printer->state_reasons & IPPEVE_PREASON_MEDIA_EMPTY) { job->printer->state_reasons |= IPPEVE_PREASON_MEDIA_NEEDED; sleep(1); } job->printer->state_reasons &= (ippeve_preason_t)~IPPEVE_PREASON_MEDIA_NEEDED; if (job->printer->command) { // Execute a command with the job spool file and wait for it to complete... int pid, // Process ID status; // Exit status struct timeval start, // Start time end; // End time char *myargv[3], // Command-line arguments *myenvp[400]; // Environment variables int myenvc; // Number of environment variables ipp_attribute_t *attr; // Job attribute char val[1280], // IPP_NAME=value *valptr; // Pointer into string #ifndef _WIN32 int mystdout = -1; // File for stdout int mypipe[2]; // Pipe for stderr char line[2048], // Line from stderr *ptr, // Pointer into line *endptr; // End of line ssize_t bytes; // Bytes read #endif // !_WIN32 fprintf(stderr, "[Job %d] Running command \"%s %s\".\n", job->id, job->printer->command, job->filename); gettimeofday(&start, NULL); // Setup the command-line arguments... myargv[0] = job->printer->command; myargv[1] = job->filename; myargv[2] = NULL; // Copy the current environment, then add environment variables for every // Job attribute and Printer -default attributes... for (myenvc = 0; environ[myenvc] && myenvc < (int)(sizeof(myenvp) / sizeof(myenvp[0]) - 1); myenvc ++) myenvp[myenvc] = strdup(environ[myenvc]); if (myenvc > (int)(sizeof(myenvp) / sizeof(myenvp[0]) - 32)) { fprintf(stderr, "[Job %d] Too many environment variables to process job.\n", job->id); job->state = IPP_JSTATE_ABORTED; goto error; } snprintf(val, sizeof(val), "CONTENT_TYPE=%s", job->format); myenvp[myenvc ++] = strdup(val); if (job->printer->device_uri) { snprintf(val, sizeof(val), "DEVICE_URI=%s", job->printer->device_uri); myenvp[myenvc ++] = strdup(val); } if (job->printer->output_format) { snprintf(val, sizeof(val), "OUTPUT_TYPE=%s", job->printer->output_format); myenvp[myenvc ++] = strdup(val); } if (job->password[0]) { snprintf(val, sizeof(val), "IPP_DOCUMENT_PASSWORD=%s", job->password); myenvp[myenvc ++] = strdup(val); } for (attr = ippGetFirstAttribute(job->printer->attrs); attr && myenvc < (int)(sizeof(myenvp) / sizeof(myenvp[0]) - 1); attr = ippGetNextAttribute(job->printer->attrs)) { // Convert "attribute-name-default" to "IPP_ATTRIBUTE_NAME_DEFAULT=", // "pclm-xxx" to "IPP_PCLM_XXX", and "pwg-xxx" to "IPP_PWG_XXX", then add // the value(s) from the attribute. const char *name = ippGetName(attr), // Attribute name *suffix = strstr(name, "-default"); // Suffix on attribute name if (strncmp(name, "pclm-", 5) && strncmp(name, "pwg-", 4) && strcmp(name, "urf-supported") && (!suffix || suffix[8])) continue; valptr = val; *valptr++ = 'I'; *valptr++ = 'P'; *valptr++ = 'P'; *valptr++ = '_'; while (*name && valptr < (val + sizeof(val) - 2)) { if (*name == '-') *valptr++ = '_'; else *valptr++ = (char)_cups_toupper(*name); name ++; } *valptr++ = '='; ippAttributeString(attr, valptr, sizeof(val) - (size_t)(valptr - val)); myenvp[myenvc++] = strdup(val); } for (attr = ippGetFirstAttribute(job->attrs); attr && myenvc < (int)(sizeof(myenvp) / sizeof(myenvp[0]) - 1); attr = ippGetNextAttribute(job->attrs)) { // Convert "attribute-name" to "IPP_ATTRIBUTE_NAME=" and then add the // value(s) from the attribute. const char *name = ippGetName(attr); // Attribute name if (!name) continue; valptr = val; *valptr++ = 'I'; *valptr++ = 'P'; *valptr++ = 'P'; *valptr++ = '_'; while (*name && valptr < (val + sizeof(val) - 2)) { if (*name == '-') *valptr++ = '_'; else *valptr++ = (char)_cups_toupper(*name); name ++; } *valptr++ = '='; ippAttributeString(attr, valptr, sizeof(val) - (size_t)(valptr - val)); myenvp[myenvc++] = strdup(val); } if (attr) { fprintf(stderr, "[Job %d] Too many environment variables to process job.\n", job->id); job->state = IPP_JSTATE_ABORTED; goto error; } myenvp[myenvc] = NULL; // Now run the program... #ifdef _WIN32 status = _spawnvpe(_P_WAIT, job->printer->command, myargv, myenvp); #else if (job->printer->device_uri) { char scheme[32], // URI scheme userpass[256], // username:password (unused) host[256], // Hostname or IP address resource[256]; // Resource path int port; // Port number if (httpSeparateURI(HTTP_URI_CODING_ALL, job->printer->device_uri, scheme, sizeof(scheme), userpass, sizeof(userpass), host, sizeof(host), &port, resource, sizeof(resource)) < HTTP_URI_STATUS_OK) { fprintf(stderr, "[Job %d] Bad device URI '%s'.\n", job->id, job->printer->device_uri); } else if (!strcmp(scheme, "file")) { struct stat fileinfo; // See if this is a file or directory... if (stat(resource, &fileinfo)) { if (errno == ENOENT) { if ((mystdout = open(resource, O_WRONLY | O_CREAT | O_TRUNC | O_BINARY, 0666)) >= 0) fprintf(stderr, "[Job %d] Saving print command output to '%s'.\n", job->id, resource); else fprintf(stderr, "[Job %d] Unable to create '%s': %s\n", job->id, resource, strerror(errno)); } else { fprintf(stderr, "[Job %d] Unable to access '%s': %s\n", job->id, resource, strerror(errno)); } } else if (S_ISDIR(fileinfo.st_mode)) { if ((mystdout = create_job_file(job, line, sizeof(line), resource, "prn")) >= 0) fprintf(stderr, "[Job %d] Saving print command output to '%s'.\n", job->id, line); else fprintf(stderr, "[Job %d] Unable to create '%s': %s\n", job->id, line, strerror(errno)); } else if (!S_ISREG(fileinfo.st_mode)) { if ((mystdout = open(resource, O_WRONLY | O_CREAT | O_TRUNC | O_BINARY, 0666)) >= 0) fprintf(stderr, "[Job %d] Saving print command output to '%s'.\n", job->id, resource); else fprintf(stderr, "[Job %d] Unable to create '%s': %s\n", job->id, resource, strerror(errno)); } else if ((mystdout = open(resource, O_WRONLY | O_BINARY)) >= 0) { fprintf(stderr, "[Job %d] Saving print command output to '%s'.\n", job->id, resource); } else { fprintf(stderr, "[Job %d] Unable to open '%s': %s\n", job->id, resource, strerror(errno)); } } else if (!strcmp(scheme, "socket")) { http_addrlist_t *addrlist; // List of addresses char service[32]; // Service number snprintf(service, sizeof(service), "%d", port); if ((addrlist = httpAddrGetList(host, AF_UNSPEC, service)) == NULL) fprintf(stderr, "[Job %d] Unable to find '%s': %s\n", job->id, host, cupsGetErrorString()); else if (!httpAddrConnect2(addrlist, &mystdout, 30000, &(job->cancel))) fprintf(stderr, "[Job %d] Unable to connect to '%s' on port %d: %s\n", job->id, host, port, cupsGetErrorString()); httpAddrFreeList(addrlist); } else { fprintf(stderr, "[Job %d] Unsupported device URI scheme '%s'.\n", job->id, scheme); } } else if ((mystdout = create_job_file(job, line, sizeof(line), job->printer->directory, "prn")) >= 0) { fprintf(stderr, "[Job %d] Saving print command output to '%s'.\n", job->id, line); } if (mystdout < 0) { if ((mystdout = open("/dev/null", O_WRONLY | O_BINARY)) < 0) fprintf(stderr, "[Job %d] Unable to redirect command output to /dev/null: %s", job->id, strerror(errno)); } if (pipe(mypipe)) { fprintf(stderr, "[Job %d] Unable to create pipe for stderr: %s\n", job->id, strerror(errno)); mypipe[0] = mypipe[1] = -1; } if ((pid = fork()) == 0) { // Child comes here... if (mystdout >= 0) { close(1); dup2(mystdout, 1); close(mystdout); } if (mypipe[1] >= 0) { close(2); dup2(mypipe[1], 2); close(mypipe[0]); close(mypipe[1]); } execve(job->printer->command, myargv, myenvp); exit(errno); } else if (pid < 0) { // Unable to fork process... fprintf(stderr, "[Job %d] Unable to start job processing command: %s\n", job->id, strerror(errno)); status = -1; if (mystdout >= 0) close(mystdout); if (mypipe[0] >= 0) { close(mypipe[0]); close(mypipe[1]); } // Free memory used for environment... while (myenvc > 0) free(myenvp[-- myenvc]); } else { // Free memory used for environment... while (myenvc > 0) free(myenvp[-- myenvc]); // Close the output file in the parent process... if (mystdout >= 0) close(mystdout); // If the pipe exists, read from it until EOF... if (mypipe[0] >= 0) { close(mypipe[1]); endptr = line; while ((bytes = read(mypipe[0], endptr, sizeof(line) - (size_t)(endptr - line) - 1)) > 0) { endptr += bytes; *endptr = '\0'; while ((ptr = strchr(line, '\n')) != NULL) { int level = 3; // Message log level *ptr++ = '\0'; if (!strncmp(line, "ATTR:", 5)) { // Process job/printer attribute updates. process_attr_message(job, line); } else if (!strncmp(line, "DEBUG:", 6)) { // Debug message... level = 2; } else if (!strncmp(line, "ERROR:", 6)) { // Error message... level = 0; job->message = strdup(line + 6); job->msglevel = 0; } else if (!strncmp(line, "INFO:", 5)) { // Informational/progress message... level = 1; if (job->msglevel) { job->message = strdup(line + 5); job->msglevel = 1; } } else if (!strncmp(line, "STATE:", 6)) { // Process printer-state-reasons keywords. process_state_message(job, line); } if (Verbosity >= level) fprintf(stderr, "[Job %d] Command - %s\n", job->id, line); bytes = ptr - line; if (ptr < endptr) memmove(line, ptr, (size_t)(endptr - ptr)); endptr -= bytes; *endptr = '\0'; } } close(mypipe[0]); } // Wait for child to complete... # ifdef HAVE_WAITPID while (waitpid(pid, &status, 0) < 0); # else while (wait(&status) < 0); # endif // HAVE_WAITPID } #endif // _WIN32 if (status) { #ifndef _WIN32 if (WIFEXITED(status)) #endif // !_WIN32 fprintf(stderr, "[Job %d] Command \"%s\" exited with status %d.\n", job->id, job->printer->command, WEXITSTATUS(status)); #ifndef _WIN32 else fprintf(stderr, "[Job %d] Command \"%s\" terminated with signal %d.\n", job->id, job->printer->command, WTERMSIG(status)); #endif // !_WIN32 job->state = IPP_JSTATE_ABORTED; } else { fprintf(stderr, "[Job %d] Command \"%s\" completed successfully.\n", job->id, job->printer->command); } // Report the total processing time... gettimeofday(&end, NULL); fprintf(stderr, "[Job %d] Processing time was %.3f seconds.\n", job->id, (double)(end.tv_sec - start.tv_sec) + 0.000001 * (double)(end.tv_usec - start.tv_usec)); } else { // Sleep for a random amount of time to simulate job processing. sleep((unsigned)(5 + (cupsGetRand() % 11))); } if (job->cancel) job->state = IPP_JSTATE_CANCELED; else if (job->state == IPP_JSTATE_PROCESSING) job->state = IPP_JSTATE_COMPLETED; error: job->completed = time(NULL); job->printer->state = IPP_PSTATE_IDLE; job->printer->active_job = NULL; return (NULL); } // // 'process_state_message()' - Process a STATE: message from a command. // static void process_state_message( ippeve_job_t *job, // I - Job char *message) // I - Message { int i; // Looping var ippeve_preason_t state_reasons, // printer-state-reasons values bit; // Current reason bit char *ptr, // Pointer into message *next; // Next keyword in message int remove; // Non-zero if we are removing keywords // Skip leading "STATE:" and any whitespace... for (message += 6; *message; message ++) { if (*message != ' ' && *message != '\t') break; } // Support the following forms of message: // // "keyword[,keyword,...]" to set the printer-state-reasons value(s). // // "-keyword[,keyword,...]" to remove keywords. // // "+keyword[,keyword,...]" to add keywords. // // Keywords may or may not have a suffix (-report, -warning, -error) per // RFC 8011. if (*message == '-') { remove = 1; state_reasons = job->printer->state_reasons; message ++; } else if (*message == '+') { remove = 0; state_reasons = job->printer->state_reasons; message ++; } else { remove = 0; state_reasons = IPPEVE_PREASON_NONE; } while (*message) { if ((next = strchr(message, ',')) != NULL) *next++ = '\0'; if ((ptr = strstr(message, "-error")) != NULL) *ptr = '\0'; else if ((ptr = strstr(message, "-report")) != NULL) *ptr = '\0'; else if ((ptr = strstr(message, "-warning")) != NULL) *ptr = '\0'; for (i = 0, bit = 1; i < (int)(sizeof(ippeve_preason_strings) / sizeof(ippeve_preason_strings[0])); i ++, bit *= 2) { if (!strcmp(message, ippeve_preason_strings[i])) { if (remove) state_reasons &= ~bit; else state_reasons |= bit; } } if (next) message = next; else break; } job->printer->state_reasons = state_reasons; } // // 'register_printer()' - Register a printer object via DNS-SD. // static bool // O - `true` on success, `false` on error register_printer( ippeve_printer_t *printer) // I - Printer { int num_txt; // Number of DNS-SD IPP TXT key/value pairs cups_option_t *txt; // DNS-SD IPP TXT key/value pairs uint32_t if_index; // Interface index int i, // Looping var count; // Number of values ipp_attribute_t *color_supported, *document_format_supported, *printer_location, *printer_make_and_model, *printer_uuid, *sides_supported, *urf_supported; // Printer attributes const char *value; // Value string char regtype[256], // Registration types adminurl[247], // adminurl value formats[252], // List of supported formats urf[252], // List of supported URF values *ptr; // Pointer into string if (printer->dnssd_subtypes && !strcmp(printer->dnssd_subtypes, "off")) return (true); color_supported = ippFindAttribute(printer->attrs, "color-supported", IPP_TAG_BOOLEAN); document_format_supported = ippFindAttribute(printer->attrs, "document-format-supported", IPP_TAG_MIMETYPE); printer_location = ippFindAttribute(printer->attrs, "printer-location", IPP_TAG_TEXT); printer_make_and_model = ippFindAttribute(printer->attrs, "printer-make-and-model", IPP_TAG_TEXT); printer_uuid = ippFindAttribute(printer->attrs, "printer-uuid", IPP_TAG_URI); sides_supported = ippFindAttribute(printer->attrs, "sides-supported", IPP_TAG_KEYWORD); urf_supported = ippFindAttribute(printer->attrs, "urf-supported", IPP_TAG_KEYWORD); httpAssembleURI(HTTP_URI_CODING_ALL, adminurl, sizeof(adminurl), WEB_SCHEME, NULL, printer->hostname, printer->port, "/"); for (i = 0, count = ippGetCount(document_format_supported), ptr = formats; i < count; i ++) { value = ippGetString(document_format_supported, i, NULL); if (!strcasecmp(value, "application/octet-stream")) continue; if (ptr > formats && ptr < (formats + sizeof(formats) - 1)) *ptr++ = ','; cupsCopyString(ptr, value, sizeof(formats) - (size_t)(ptr - formats)); ptr += strlen(ptr); if (ptr >= (formats + sizeof(formats) - 1)) break; } urf[0] = '\0'; for (i = 0, count = ippGetCount(urf_supported), ptr = urf; i < count; i ++) { value = ippGetString(urf_supported, i, NULL); if (ptr > urf && ptr < (urf + sizeof(urf) - 1)) *ptr++ = ','; cupsCopyString(ptr, value, sizeof(urf) - (size_t)(ptr - urf)); ptr += strlen(ptr); if (ptr >= (urf + sizeof(urf) - 1)) break; } // Rename the service as needed... if (printer->dnssd_collision) { char new_dnssd_name[256]; // New DNS-SD name const char *uuid = ippGetString(printer_uuid, 0, NULL); // "printer-uuid" value cupsRWLockWrite(&printer->rwlock); snprintf(new_dnssd_name, sizeof(new_dnssd_name), "%s (%c%c%c%c%c%c)", printer->dnssd_name, toupper(uuid[39]), toupper(uuid[40]), toupper(uuid[41]), toupper(uuid[42]), toupper(uuid[43]), toupper(uuid[44])); free(printer->dnssd_name); printer->dnssd_name = strdup(new_dnssd_name); fprintf(stderr, "DNS-SD name collision, trying new DNS-SD service name '%s'.\n", printer->dnssd_name); cupsRWUnlock(&printer->rwlock); printer->dnssd_collision = 0; } // Build the TXT record for IPP... num_txt = cupsAddOption("rp", "ipp/print", 0, &txt); if ((value = ippGetString(printer_make_and_model, 0, NULL)) != NULL) num_txt = cupsAddOption("ty", value, num_txt, &txt); num_txt = cupsAddOption("adminurl", adminurl, num_txt, &txt); if ((value = ippGetString(printer_location, 0, NULL)) != NULL) num_txt = cupsAddOption("note", value, num_txt, &txt); num_txt = cupsAddOption("pdl", formats, num_txt, &txt); num_txt = cupsAddOption("Color", ippGetBoolean(color_supported, 0) ? "T" : "F", num_txt, &txt); num_txt = cupsAddOption("Duplex", ippGetCount(sides_supported) > 1 ? "T" : "F", num_txt, &txt); if ((value = ippGetString(printer_uuid, 0, NULL)) != NULL) num_txt = cupsAddOption("UUID", value + 9, num_txt, &txt); num_txt = cupsAddOption("TLS", "1.3", num_txt, &txt); if (urf[0]) num_txt = cupsAddOption("URF", urf, num_txt, &txt); num_txt = cupsAddOption("txtvers", "1", num_txt, &txt); num_txt = cupsAddOption("qtotal", "1", num_txt, &txt); // Register the _printer._tcp (LPD) service type with a port number of 0 to // defend our service name but not actually support LPD... if_index = !strcmp(printer->hostname, "localhost") ? CUPS_DNSSD_IF_INDEX_LOCAL : CUPS_DNSSD_IF_INDEX_ANY; cupsDNSSDServiceDelete(printer->services); if ((printer->services = cupsDNSSDServiceNew(printer->dnssd, if_index, printer->dnssd_name, (cups_dnssd_service_cb_t)dnssd_callback, printer)) == NULL) goto error; if (!cupsDNSSDServiceAdd(printer->services, "_printer._tcp", /*domain*/NULL, printer->hostname, /*port*/0, /*num_txt*/0, /*txt*/NULL)) goto error; // Then register the _ipp._tcp (IPP) service type with the real port number to // advertise our IPP printer... if (printer->dnssd_subtypes && *(printer->dnssd_subtypes)) snprintf(regtype, sizeof(regtype), "_ipp._tcp,%s", printer->dnssd_subtypes); else cupsCopyString(regtype, "_ipp._tcp", sizeof(regtype)); if (!cupsDNSSDServiceAdd(printer->services, regtype, /*domain*/NULL, printer->hostname, (uint16_t)printer->port, num_txt, txt)) goto error; // Then register the _ipps._tcp (IPP) service type with the real port number // to advertise our IPPS printer... if (printer->dnssd_subtypes && *(printer->dnssd_subtypes)) snprintf(regtype, sizeof(regtype), "_ipps._tcp,%s", printer->dnssd_subtypes); else cupsCopyString(regtype, "_ipps._tcp", sizeof(regtype)); if (!cupsDNSSDServiceAdd(printer->services, regtype, /*domain*/NULL, printer->hostname, (uint16_t)printer->port, num_txt, txt)) goto error; // Similarly, register the _http._tcp,_printer (HTTP) service type with the // real port number to advertise our IPP printer's web interface... if (!cupsDNSSDServiceAdd(printer->services, "_http._tcp,_printer", /*domain*/NULL, printer->hostname, (uint16_t)printer->port, num_txt, txt)) goto error; cupsFreeOptions(num_txt, txt); // Commit it... return (cupsDNSSDServicePublish(printer->services)); // If we get here there was a problem... error: cupsFreeOptions(num_txt, txt); return (false); } // // 'respond_http()' - Send a HTTP response. // bool // O - `true` on success, `false` on failure respond_http( ippeve_client_t *client, // I - Client http_status_t code, // I - HTTP status of response const char *content_encoding, // I - Content-Encoding of response const char *type, // I - MIME media type of response size_t length) // I - Length of response { char message[1024]; // Text message fprintf(stderr, "%s %s\n", client->hostname, httpStatusString(code)); if (code == HTTP_STATUS_CONTINUE) { // 100-continue doesn't send any headers... return (!httpWriteResponse(client->http, HTTP_STATUS_CONTINUE)); } // Format an error message... if (!type && !length && code != HTTP_STATUS_OK && code != HTTP_STATUS_SWITCHING_PROTOCOLS) { snprintf(message, sizeof(message), "%d - %s\n", code, httpStatusString(code)); type = "text/plain"; length = strlen(message); } else message[0] = '\0'; // Send the HTTP response header... httpClearFields(client->http); if (code == HTTP_STATUS_METHOD_NOT_ALLOWED || client->operation == HTTP_STATE_OPTIONS) httpSetField(client->http, HTTP_FIELD_ALLOW, "GET, HEAD, OPTIONS, POST"); if (code == HTTP_STATUS_UNAUTHORIZED) { char value[256]; // WWW-Authenticate value snprintf(value, sizeof(value), "Basic realm=\"%s\"", PAMService); httpSetField(client->http, HTTP_FIELD_WWW_AUTHENTICATE, value); } if (type) { if (!strcmp(type, "text/html")) httpSetField(client->http, HTTP_FIELD_CONTENT_TYPE, "text/html; charset=utf-8"); else httpSetField(client->http, HTTP_FIELD_CONTENT_TYPE, type); if (content_encoding) httpSetField(client->http, HTTP_FIELD_CONTENT_ENCODING, content_encoding); } httpSetLength(client->http, length); if (httpWriteResponse(client->http, code)) return (false); // Send the response data... if (message[0]) { // Send a plain text message. if (httpPrintf(client->http, "%s", message) < 0) return (false); if (httpWrite(client->http, "", 0) < 0) return (false); } else if (client->response) { // Send an IPP response... debug_attributes("Response", client->response, 2); ippSetState(client->response, IPP_STATE_IDLE); if (ippWrite(client->http, client->response) != IPP_STATE_DATA) return (false); } return (true); } // // 'respond_ignored()' - Respond with an ignored attribute. // static void respond_ignored( ippeve_client_t *client, // I - Client ipp_attribute_t *attr) // I - Attribute { ipp_attribute_t *temp; // Copy of attribute if (!ippGetStatusCode(client->response)) respond_ipp(client, IPP_STATUS_OK_IGNORED_OR_SUBSTITUTED, "Unsupported %s %s%s value.", ippGetName(attr), ippGetCount(attr) > 1 ? "1setOf " : "", ippTagString(ippGetValueTag(attr))); temp = ippCopyAttribute(client->response, attr, 0); ippSetGroupTag(client->response, &temp, IPP_TAG_UNSUPPORTED_GROUP); } // // 'respond_ipp()' - Send an IPP response. // static void respond_ipp(ippeve_client_t *client, // I - Client ipp_status_t status, // I - status-code const char *message, // I - printf-style status-message ...) // I - Additional args as needed { const char *formatted = NULL; // Formatted message if (ippGetStatusCode(client->response) == IPP_STATUS_OK) ippSetStatusCode(client->response, status); if (message) { va_list ap; // Pointer to additional args ipp_attribute_t *attr; // New status-message attribute va_start(ap, message); if ((attr = ippFindAttribute(client->response, "status-message", IPP_TAG_TEXT)) != NULL) ippSetStringfv(client->response, &attr, 0, message, ap); else attr = ippAddStringfv(client->response, IPP_TAG_OPERATION, IPP_TAG_TEXT, "status-message", NULL, message, ap); va_end(ap); formatted = ippGetString(attr, 0, NULL); } if (formatted) fprintf(stderr, "%s %s %s (%s)\n", client->hostname, ippOpString(client->operation_id), ippErrorString(status), formatted); else fprintf(stderr, "%s %s %s\n", client->hostname, ippOpString(client->operation_id), ippErrorString(status)); } // // 'respond_unsupported()' - Respond with an unsupported attribute. // static void respond_unsupported( ippeve_client_t *client, // I - Client ipp_attribute_t *attr) // I - Atribute { ipp_attribute_t *temp; // Copy of attribute respond_ipp(client, IPP_STATUS_ERROR_ATTRIBUTES_OR_VALUES, "Unsupported %s %s%s value.", ippGetName(attr), ippGetCount(attr) > 1 ? "1setOf " : "", ippTagString(ippGetValueTag(attr))); temp = ippCopyAttribute(client->response, attr, 0); ippSetGroupTag(client->response, &temp, IPP_TAG_UNSUPPORTED_GROUP); } // // 'run_printer()' - Run the printer service. // static void run_printer(ippeve_printer_t *printer) // I - Printer { int num_fds; // Number of file descriptors struct pollfd polldata[3]; // poll() data ippeve_client_t *client; // New client #ifndef _WIN32 // Set signal handlers for SIGINT and SIGTERM... signal(SIGINT, signal_handler); signal(SIGTERM, signal_handler); #endif // !_WIN32 // Setup poll() data for the DNS-SD service socket and IPv4/6 listeners... polldata[0].fd = printer->ipv4; polldata[0].events = POLLIN; polldata[1].fd = printer->ipv6; polldata[1].events = POLLIN; num_fds = 2; // Loop until we are killed or have a hard error... for (;;) { if (poll(polldata, (nfds_t)num_fds, 1000) < 0 && errno != EINTR) { perror("poll() failed"); break; } #ifndef _WIN32 if (StopPrinter) break; #endif // !_WIN32 if (polldata[0].revents & POLLIN) { if ((client = create_client(printer, printer->ipv4)) != NULL) { cups_thread_t t; // Client thread if ((t = cupsThreadCreate((cups_thread_func_t)process_client, client)) == CUPS_THREAD_INVALID) { perror("Unable to create client thread"); delete_client(client); } else { cupsThreadDetach(t); } } } if (polldata[1].revents & POLLIN) { if ((client = create_client(printer, printer->ipv6)) != NULL) { cups_thread_t t; // Client thread if ((t = cupsThreadCreate((cups_thread_func_t)process_client, client)) == CUPS_THREAD_INVALID) { perror("Unable to create client thread"); delete_client(client); } else { cupsThreadDetach(t); } } } if (printer->dnssd_collision) register_printer(printer); // Clean out old jobs... clean_jobs(printer); } } // // 'show_media()' - Show media load state. // static int // O - 1 on success, 0 on failure show_media(ippeve_client_t *client) // I - Client connection { ippeve_printer_t *printer = client->printer; // Printer int i, j, // Looping vars num_ready, // Number of ready media num_sizes, // Number of media sizes num_sources, // Number of media sources num_types; // Number of media types ipp_attribute_t *media_col_ready,// media-col-ready attribute *media_ready, // media-ready attribute *media_sizes, // media-supported attribute *media_sources, // media-source-supported attribute *media_types, // media-type-supported attribute *input_tray; // printer-input-tray attribute ipp_t *media_col; // media-col value const char *media_size, // media value *media_source, // media-source value *media_type, // media-type value *ready_size, // media-col-ready media-size[-name] value *ready_source, // media-col-ready media-source value *ready_tray, // printer-input-tray value *ready_type; // media-col-ready media-type value char tray_str[1024], // printer-input-tray string value *tray_ptr; // Pointer into value int tray_len; // Length of printer-input-tray value int ready_sheets; // printer-input-tray sheets value int num_options = 0;// Number of form options cups_option_t *options = NULL;// Form options static const int sheets[] = // Number of sheets { 250, 125, 50, 25, 5, 0, -2 }; if (!respond_http(client, HTTP_STATUS_OK, NULL, "text/html", 0)) return (0); html_header(client, printer->name, 0); if ((media_col_ready = ippFindAttribute(printer->attrs, "media-col-ready", IPP_TAG_BEGIN_COLLECTION)) == NULL) { html_printf(client, "

Error: No media-col-ready defined for printer.

\n"); html_footer(client); return (1); } media_ready = ippFindAttribute(printer->attrs, "media-ready", IPP_TAG_ZERO); if ((media_sizes = ippFindAttribute(printer->attrs, "media-supported", IPP_TAG_ZERO)) == NULL) { html_printf(client, "

Error: No media-supported defined for printer.

\n"); html_footer(client); return (1); } if ((media_sources = ippFindAttribute(printer->attrs, "media-source-supported", IPP_TAG_ZERO)) == NULL) { html_printf(client, "

Error: No media-source-supported defined for printer.

\n"); html_footer(client); return (1); } if ((media_types = ippFindAttribute(printer->attrs, "media-type-supported", IPP_TAG_ZERO)) == NULL) { html_printf(client, "

Error: No media-type-supported defined for printer.

\n"); html_footer(client); return (1); } if ((input_tray = ippFindAttribute(printer->attrs, "printer-input-tray", IPP_TAG_STRING)) == NULL) { html_printf(client, "

Error: No printer-input-tray defined for printer.

\n"); html_footer(client); return (1); } num_ready = ippGetCount(media_col_ready); num_sizes = ippGetCount(media_sizes); num_sources = ippGetCount(media_sources); num_types = ippGetCount(media_types); if (num_sources != ippGetCount(input_tray)) { html_printf(client, "

Error: Different number of trays in media-source-supported and printer-input-tray defined for printer.

\n"); html_footer(client); return (1); } // Process form data if present... if (printer->web_forms) num_options = parse_options(client, &options); if (num_options > 0) { // WARNING: A real printer/server implementation MUST NOT implement // media updates via a GET request - GET requests are supposed to be // idempotent (without side-effects) and we obviously are not // authenticating access here. This form is provided solely to // enable testing and development! char name[255]; // Form name const char *val; // Form value pwg_media_t *media; // Media info cupsRWLockWrite(&printer->rwlock); ippDeleteAttribute(printer->attrs, media_col_ready); media_col_ready = NULL; if (media_ready) { ippDeleteAttribute(printer->attrs, media_ready); media_ready = NULL; } printer->state_reasons &= (ippeve_preason_t)~(IPPEVE_PREASON_MEDIA_LOW | IPPEVE_PREASON_MEDIA_EMPTY | IPPEVE_PREASON_MEDIA_NEEDED); for (i = 0; i < num_sources; i ++) { media_source = ippGetString(media_sources, i, NULL); if (!strcmp(media_source, "auto") || !strcmp(media_source, "manual") || strstr(media_source, "-man") != NULL) continue; snprintf(name, sizeof(name), "size%u", (unsigned)i); if ((media_size = cupsGetOption(name, num_options, options)) != NULL && (media = pwgMediaForPWG(media_size)) != NULL) { snprintf(name, sizeof(name), "type%u", (unsigned)i); if ((media_type = cupsGetOption(name, num_options, options)) != NULL && !*media_type) media_type = NULL; if (media_ready) ippSetString(printer->attrs, &media_ready, ippGetCount(media_ready), media_size); else media_ready = ippAddString(printer->attrs, IPP_TAG_PRINTER, IPP_TAG_KEYWORD, "media-ready", NULL, media_size); media_col = create_media_col(media_size, media_source, media_type, create_media_size(media->width, media->length), -1, -1, -1, -1); if (media_col_ready) ippSetCollection(printer->attrs, &media_col_ready, ippGetCount(media_col_ready), media_col); else media_col_ready = ippAddCollection(printer->attrs, IPP_TAG_PRINTER, "media-col-ready", media_col); ippDelete(media_col); } else media = NULL; snprintf(name, sizeof(name), "level%u", (unsigned)i); if ((val = cupsGetOption(name, num_options, options)) != NULL) ready_sheets = atoi(val); else ready_sheets = 0; if (!strcmp(media_source, "roll")) snprintf(tray_str, sizeof(tray_str), "type=continuousRoll;mediafeed=%d;mediaxfeed=%d;maxcapacity=%d;level=%d;status=0;name=%s;", media ? media->length : 0, media ? media->width : 0, 100, ready_sheets, media_source); else snprintf(tray_str, sizeof(tray_str), "type=sheetFeedAuto%sRemovableTray;mediafeed=%d;mediaxfeed=%d;maxcapacity=%d;level=%d;status=0;name=%s;", !strcmp(media_source, "by-pass-tray") ? "Non" : "", media ? media->length : 0, media ? media->width : 0, strcmp(media_source, "by-pass-tray") ? 250 : 25, ready_sheets, media_source); ippSetOctetString(printer->attrs, &input_tray, i, tray_str, strlen(tray_str)); if (ready_sheets == 0) { printer->state_reasons |= IPPEVE_PREASON_MEDIA_EMPTY; if (printer->active_job) printer->state_reasons |= IPPEVE_PREASON_MEDIA_NEEDED; } else if (ready_sheets < 25 && ready_sheets > 0) printer->state_reasons |= IPPEVE_PREASON_MEDIA_LOW; } if (!media_col_ready) media_col_ready = ippAddOutOfBand(printer->attrs, IPP_TAG_PRINTER, IPP_TAG_NOVALUE, "media-col-ready"); if (!media_ready) media_ready = ippAddOutOfBand(printer->attrs, IPP_TAG_PRINTER, IPP_TAG_NOVALUE, "media-ready"); cupsRWUnlock(&printer->rwlock); } if (printer->web_forms) html_printf(client, "
\n"); html_printf(client, "\n"); for (i = 0; i < num_sources; i ++) { media_source = ippGetString(media_sources, i, NULL); if (!strcmp(media_source, "auto") || !strcmp(media_source, "manual") || strstr(media_source, "-man") != NULL) continue; for (j = 0, ready_size = NULL, ready_type = NULL; j < num_ready; j ++) { media_col = ippGetCollection(media_col_ready, j); ready_size = ippGetString(ippFindAttribute(media_col, "media-size-name", IPP_TAG_ZERO), 0, NULL); ready_source = ippGetString(ippFindAttribute(media_col, "media-source", IPP_TAG_ZERO), 0, NULL); ready_type = ippGetString(ippFindAttribute(media_col, "media-type", IPP_TAG_ZERO), 0, NULL); if (!ready_size) ready_size = ippGetString(media_ready, j, NULL); if (ready_source && !strcmp(ready_source, media_source)) break; ready_size = NULL; ready_type = NULL; } html_printf(client, "", media_source); // Media size... if (printer->web_forms) { html_printf(client, "\n"); } else if (ready_sheets == 1) html_printf(client, ", 1 sheet\n"); else if (ready_sheets > 0) html_printf(client, ", %d sheets\n", ready_sheets); else html_printf(client, "\n"); } if (printer->web_forms) { html_printf(client, "
%s:"); } else html_printf(client, "%s", ready_size); // Media type... if (printer->web_forms) { html_printf(client, " "); } else if (ready_type) html_printf(client, ", %s", ready_type); // Level/sheets loaded... if ((ready_tray = ippGetOctetString(input_tray, i, &tray_len)) != NULL) { if (tray_len > (int)(sizeof(tray_str) - 1)) tray_len = (int)sizeof(tray_str) - 1; memcpy(tray_str, ready_tray, (size_t)tray_len); tray_str[tray_len] = '\0'; if ((tray_ptr = strstr(tray_str, "level=")) != NULL) ready_sheets = atoi(tray_ptr + 6); else ready_sheets = 0; } else ready_sheets = 0; if (printer->web_forms) { html_printf(client, "
"); if (num_options > 0) html_printf(client, " Media updated.\n"); html_printf(client, "
\n"); if (num_options > 0) html_printf(client, "\n"); } else html_printf(client, "\n"); cupsFreeOptions(num_options, options); html_footer(client); return (1); } // // 'show_status()' - Show printer/system state. // static int // O - 1 on success, 0 on failure show_status(ippeve_client_t *client) // I - Client connection { ippeve_printer_t *printer = client->printer; // Printer ippeve_job_t *job; // Current job size_t i; // Looping var ippeve_preason_t reason; // Current reason static const char * const reasons[] = // Reason strings { "Other", "Cover Open", "Input Tray Missing", "Marker Supply Empty", "Marker Supply Low", "Marker Waste Almost Full", "Marker Waste Full", "Media Empty", "Media Jam", "Media Low", "Media Needed", "Moving to Paused", "Paused", "Spool Area Full", "Toner Empty", "Toner Low" }; static const char * const state_colors[] = { // State colors "#0C0", // Idle "#EE0", // Processing "#C00" // Stopped }; if (!respond_http(client, HTTP_STATUS_OK, NULL, "text/html", 0)) return (0); html_header(client, printer->name, printer->state == IPP_PSTATE_PROCESSING ? 5 : 15); html_printf(client, "

%s Jobs

\n", state_colors[printer->state - IPP_PSTATE_IDLE], printer->name); html_printf(client, "

%s, %u job(s).", printer->state == IPP_PSTATE_IDLE ? "Idle" : printer->state == IPP_PSTATE_PROCESSING ? "Printing" : "Stopped", (unsigned)cupsArrayGetCount(printer->jobs)); for (i = 0, reason = 1; i < (sizeof(reasons) / sizeof(reasons[0])); i ++, reason <<= 1) if (printer->state_reasons & reason) html_printf(client, "\n
    %s", reasons[i]); html_printf(client, "

\n"); if (cupsArrayGetCount(printer->jobs) > 0) { cupsRWLockRead(&(printer->rwlock)); html_printf(client, "\n"); for (job = (ippeve_job_t *)cupsArrayGetFirst(printer->jobs); job; job = (ippeve_job_t *)cupsArrayGetNext(printer->jobs)) { char when[256], // When job queued/started/finished hhmmss[64]; // Time HH:MM:SS switch (job->state) { case IPP_JSTATE_PENDING : case IPP_JSTATE_HELD : snprintf(when, sizeof(when), "Queued at %s", time_string(job->created, hhmmss, sizeof(hhmmss))); break; case IPP_JSTATE_PROCESSING : case IPP_JSTATE_STOPPED : snprintf(when, sizeof(when), "Started at %s", time_string(job->processing, hhmmss, sizeof(hhmmss))); break; case IPP_JSTATE_ABORTED : snprintf(when, sizeof(when), "Aborted at %s", time_string(job->completed, hhmmss, sizeof(hhmmss))); break; case IPP_JSTATE_CANCELED : snprintf(when, sizeof(when), "Canceled at %s", time_string(job->completed, hhmmss, sizeof(hhmmss))); break; case IPP_JSTATE_COMPLETED : snprintf(when, sizeof(when), "Completed at %s", time_string(job->completed, hhmmss, sizeof(hhmmss))); break; } html_printf(client, "\n", job->id, job->name, job->username, when); } html_printf(client, "
Job #NameOwnerStatus
%d%s%s%s
\n"); cupsRWUnlock(&(printer->rwlock)); } html_footer(client); return (1); } // // 'show_supplies()' - Show printer supplies. // static int // O - 1 on success, 0 on failure show_supplies( ippeve_client_t *client) // I - Client connection { ippeve_printer_t *printer = client->printer; // Printer int i, // Looping var num_supply; // Number of supplies ipp_attribute_t *supply, // printer-supply attribute *supply_desc; // printer-supply-description attribute int num_options = 0; // Number of form options cups_option_t *options = NULL; // Form options int supply_len; // Length of supply value int level; // Supply level const char *supply_value; // Supply value char supply_text[1024], // Supply string *supply_ptr; // Pointer into supply string static const char * const printer_supply[] = { // printer-supply values "index=1;class=receptacleThatIsFilled;type=wasteToner;unit=percent;" "maxcapacity=100;level=%d;colorantname=unknown;", "index=2;class=supplyThatIsConsumed;type=toner;unit=percent;" "maxcapacity=100;level=%d;colorantname=black;", "index=3;class=supplyThatIsConsumed;type=toner;unit=percent;" "maxcapacity=100;level=%d;colorantname=cyan;", "index=4;class=supplyThatIsConsumed;type=toner;unit=percent;" "maxcapacity=100;level=%d;colorantname=magenta;", "index=5;class=supplyThatIsConsumed;type=toner;unit=percent;" "maxcapacity=100;level=%d;colorantname=yellow;" }; static const char * const backgrounds[] = { // Background colors for the supply-level bars "#777 linear-gradient(#333,#777)", "#000 linear-gradient(#666,#000)", "#0FF linear-gradient(#6FF,#0FF)", "#F0F linear-gradient(#F6F,#F0F)", "#CC0 linear-gradient(#EE6,#EE0)" }; static const char * const colors[] = // Text colors for the supply-level bars { "#fff", "#fff", "#000", "#000", "#000" }; if (!respond_http(client, HTTP_STATUS_OK, NULL, "text/html", 0)) return (0); html_header(client, printer->name, 0); if ((supply = ippFindAttribute(printer->attrs, "printer-supply", IPP_TAG_STRING)) == NULL) { html_printf(client, "

Error: No printer-supply defined for printer.

\n"); html_footer(client); return (1); } num_supply = ippGetCount(supply); if ((supply_desc = ippFindAttribute(printer->attrs, "printer-supply-description", IPP_TAG_TEXT)) == NULL) { html_printf(client, "

Error: No printer-supply-description defined for printer.

\n"); html_footer(client); return (1); } if (num_supply != ippGetCount(supply_desc)) { html_printf(client, "

Error: Different number of values for printer-supply and printer-supply-description defined for printer.

\n"); html_footer(client); return (1); } if (printer->web_forms) num_options = parse_options(client, &options); if (num_options > 0) { // WARNING: A real printer/server implementation MUST NOT implement // supply updates via a GET request - GET requests are supposed to be // idempotent (without side-effects) and we obviously are not // authenticating access here. This form is provided solely to // enable testing and development! char name[64]; // Form field const char *val; // Form value cupsRWLockWrite(&printer->rwlock); ippDeleteAttribute(printer->attrs, supply); supply = NULL; printer->state_reasons &= (ippeve_preason_t)~(IPPEVE_PREASON_MARKER_SUPPLY_EMPTY | IPPEVE_PREASON_MARKER_SUPPLY_LOW | IPPEVE_PREASON_MARKER_WASTE_ALMOST_FULL | IPPEVE_PREASON_MARKER_WASTE_FULL | IPPEVE_PREASON_TONER_EMPTY | IPPEVE_PREASON_TONER_LOW); for (i = 0; i < num_supply; i ++) { snprintf(name, sizeof(name), "supply%u", (unsigned)i); if ((val = cupsGetOption(name, num_options, options)) != NULL) { level = atoi(val); // New level if (level < 0) level = 0; else if (level > 100) level = 100; snprintf(supply_text, sizeof(supply_text), printer_supply[i], level); if (supply) ippSetOctetString(printer->attrs, &supply, ippGetCount(supply), supply_text, strlen(supply_text)); else supply = ippAddOctetString(printer->attrs, IPP_TAG_PRINTER, "printer-supply", supply_text, strlen(supply_text)); if (i == 0) { if (level == 100) printer->state_reasons |= IPPEVE_PREASON_MARKER_WASTE_FULL; else if (level > 90) printer->state_reasons |= IPPEVE_PREASON_MARKER_WASTE_ALMOST_FULL; } else { if (level == 0) printer->state_reasons |= IPPEVE_PREASON_TONER_EMPTY; else if (level < 10) printer->state_reasons |= IPPEVE_PREASON_TONER_LOW; } } } cupsRWUnlock(&printer->rwlock); } if (printer->web_forms) html_printf(client, "
\n"); html_printf(client, "\n"); for (i = 0; i < num_supply; i ++) { supply_value = ippGetOctetString(supply, i, &supply_len); if (supply_len > (int)(sizeof(supply_text) - 1)) supply_len = (int)sizeof(supply_text) - 1; memcpy(supply_text, supply_value, (size_t)supply_len); supply_text[supply_len] = '\0'; if ((supply_ptr = strstr(supply_text, "level=")) != NULL) level = atoi(supply_ptr + 6); else level = 50; if (level < 0) level = 0; else if (level > 100) level = 100; if (printer->web_forms) html_printf(client, "", ippGetString(supply_desc, i, NULL), (unsigned)i, level); else html_printf(client, "", ippGetString(supply_desc, i, NULL)); if (level < 10) html_printf(client, "\n", backgrounds[i], level * 2, level); else html_printf(client, "\n", backgrounds[i], colors[i], level * 2, level); } if (printer->web_forms) { html_printf(client, "\n
%s:
%s: %d%%
%d%%
"); if (num_options > 0) html_printf(client, " Supplies updated.\n"); html_printf(client, "
\n
\n"); if (num_options > 0) html_printf(client, "\n"); } else html_printf(client, "\n"); cupsFreeOptions(num_options, options); html_footer(client); return (1); } #ifndef _WIN32 // // 'signal_handler()' - Handle termination signals. // static void signal_handler(int signum) // I - Signal number (not used) { (void)signum; StopPrinter = 1; } #endif // !_WIN32 // // 'time_string()' - Return the local time in hours, minutes, and seconds. // static char * time_string(time_t tv, // I - Time value char *buffer, // I - Buffer size_t bufsize) // I - Size of buffer { struct tm date; // Local time and date localtime_r(&tv, &date); strftime(buffer, bufsize, "%X", &date); return (buffer); } // // 'usage()' - Show program usage. // static void usage(int status) // O - Exit status { _cupsLangPuts(stdout, _("Usage: ippeveprinter [options] \"name\"")); _cupsLangPuts(stdout, _("Options:")); _cupsLangPuts(stdout, _("--help Show program help")); _cupsLangPuts(stdout, _("--no-web-forms Disable web forms for media and supplies")); _cupsLangPuts(stdout, _("--pam-service service Use the named PAM service")); _cupsLangPuts(stdout, _("--version Show program version")); _cupsLangPuts(stdout, _("-2 Set 2-sided printing support (default=1-sided)")); _cupsLangPuts(stdout, _("-A Enable authentication")); _cupsLangPuts(stdout, _("-D device-uri Set the device URI for the printer")); _cupsLangPuts(stdout, _("-F output-type/subtype Set the output format for the printer")); _cupsLangPuts(stdout, _("-K keypath Set location of server X.509 certificates and keys.")); _cupsLangPuts(stdout, _("-M manufacturer Set manufacturer name (default=Test)")); #if !CUPS_LITE _cupsLangPuts(stdout, _("-P filename.ppd Load printer attributes from PPD file")); #endif /* !CUPS_LITE */ _cupsLangPuts(stdout, _("-S filename.strings Set strings file")); _cupsLangPuts(stdout, _("-V version Set default IPP version")); _cupsLangPuts(stdout, _("-a filename.conf Load printer attributes from conf file")); _cupsLangPuts(stdout, _("-c command Set print command")); _cupsLangPuts(stdout, _("-d spool-directory Set spool directory")); _cupsLangPuts(stdout, _("-f type/subtype[,...] Set supported file types")); _cupsLangPuts(stdout, _("-i iconfile.png[,...] Set icon file(s)")); _cupsLangPuts(stdout, _("-k Keep job spool files")); _cupsLangPuts(stdout, _("-l location Set location of printer")); _cupsLangPuts(stdout, _("-m model Set model name (default=Printer)")); _cupsLangPuts(stdout, _("-n hostname Set hostname for printer")); _cupsLangPuts(stdout, _("-p port Set port number for printer")); _cupsLangPuts(stdout, _("-r subtype,[subtype] Set DNS-SD service subtype")); _cupsLangPuts(stdout, _("-s speed[,color-speed] Set speed in pages per minute")); _cupsLangPuts(stdout, _("-v Be verbose")); exit(status); } // // 'valid_doc_attributes()' - Determine whether the document attributes are // valid. // // When one or more document attributes are invalid, this function adds a // suitable response and attributes to the unsupported group. // static bool // O - `true` if valid, `false` if not valid_doc_attributes( ippeve_client_t *client) // I - Client { bool valid = true; // Valid attributes? ipp_op_t op = ippGetOperation(client->request); // IPP operation const char *op_name = ippOpString(op); // IPP operation name ipp_attribute_t *attr, // Current attribute *supported; // xxx-supported attribute const char *compression = NULL, // compression value *format = NULL; // document-format value // Check operation attributes... if ((attr = ippFindAttribute(client->request, "compression", IPP_TAG_ZERO)) != NULL) { // If compression is specified, only accept a supported value in a Print-Job // or Send-Document request... compression = ippGetString(attr, 0, NULL); supported = ippFindAttribute(client->printer->attrs, "compression-supported", IPP_TAG_KEYWORD); if (ippGetCount(attr) != 1 || ippGetValueTag(attr) != IPP_TAG_KEYWORD || ippGetGroupTag(attr) != IPP_TAG_OPERATION || (op != IPP_OP_PRINT_JOB && op != IPP_OP_SEND_DOCUMENT && op != IPP_OP_VALIDATE_JOB) || !ippContainsString(supported, compression)) { respond_unsupported(client, attr); valid = false; } else { fprintf(stderr, "%s %s compression=\"%s\"\n", client->hostname, op_name, compression); ippAddString(client->request, IPP_TAG_JOB, IPP_TAG_KEYWORD, "compression-supplied", NULL, compression); if (strcmp(compression, "none")) { if (Verbosity) fprintf(stderr, "Receiving job file with \"%s\" compression.\n", compression); httpSetField(client->http, HTTP_FIELD_CONTENT_ENCODING, compression); } } } // Is it a format we support? if ((attr = ippFindAttribute(client->request, "document-format", IPP_TAG_ZERO)) != NULL) { if (ippGetCount(attr) != 1 || ippGetValueTag(attr) != IPP_TAG_MIMETYPE || ippGetGroupTag(attr) != IPP_TAG_OPERATION) { respond_unsupported(client, attr); valid = false; } else { format = ippGetString(attr, 0, NULL); fprintf(stderr, "%s %s document-format=\"%s\"\n", client->hostname, op_name, format); ippAddString(client->request, IPP_TAG_JOB, IPP_TAG_MIMETYPE, "document-format-supplied", NULL, format); } } else { format = ippGetString(ippFindAttribute(client->printer->attrs, "document-format-default", IPP_TAG_MIMETYPE), 0, NULL); if (!format) format = "application/octet-stream"; // Should never happen attr = ippAddString(client->request, IPP_TAG_OPERATION, IPP_TAG_MIMETYPE, "document-format", NULL, format); } if (format && !strcmp(format, "application/octet-stream") && (ippGetOperation(client->request) == IPP_OP_PRINT_JOB || ippGetOperation(client->request) == IPP_OP_SEND_DOCUMENT)) { // Auto-type the file using the first 8 bytes of the file... unsigned char header[8]; // First 8 bytes of file memset(header, 0, sizeof(header)); httpPeek(client->http, (char *)header, sizeof(header)); fprintf(stderr, "%s %s Auto-type header: %02X%02X%02X%02X%02X%02X%02X%02X\n", client->hostname, op_name, header[0], header[1], header[2], header[3], header[4], header[5], header[6], header[7]); if (!memcmp(header, "%PDF", 4)) format = "application/pdf"; else if (!memcmp(header, "%!", 2)) format = "application/postscript"; else if (!memcmp(header, "\377\330\377", 3) && header[3] >= 0xe0 && header[3] <= 0xef) format = "image/jpeg"; else if (!memcmp(header, "\211PNG", 4)) format = "image/png"; else if (!memcmp(header, "RaS2PwgR", 8)) format = "image/pwg-raster"; else if (!memcmp(header, "UNIRAST", 8)) format = "image/urf"; else format = NULL; if (format) { fprintf(stderr, "%s %s Auto-typed document-format=\"%s\"\n", client->hostname, op_name, format); ippAddString(client->request, IPP_TAG_JOB, IPP_TAG_MIMETYPE, "document-format-detected", NULL, format); } } if (op != IPP_OP_CREATE_JOB && (supported = ippFindAttribute(client->printer->attrs, "document-format-supported", IPP_TAG_MIMETYPE)) != NULL && !ippContainsString(supported, format)) { respond_unsupported(client, attr); valid = false; } // document-name if ((attr = ippFindAttribute(client->request, "document-name", IPP_TAG_NAME)) != NULL) ippAddString(client->request, IPP_TAG_JOB, IPP_TAG_NAME, "document-name-supplied", NULL, ippGetString(attr, 0, NULL)); return (valid); } // // 'valid_job_attributes()' - Determine whether the job attributes are valid. // // When one or more job attributes are invalid, this function adds a suitable // response and attributes to the unsupported group. // static bool // O - `true` if valid, `false` if not valid_job_attributes( ippeve_client_t *client) // I - Client { int i, // Looping var count; // Number of values bool fidelity, // "ipp-attribute-fidelity" value valid = true; // Valid attributes? ipp_attribute_t *attr, // Current attribute *attr_col, // xxx-col attribute *supported; // xxx-supported attribute // Check operation attributes... valid = valid_doc_attributes(client); // Check the various job template attributes... if ((attr = ippFindAttribute(client->request, "ipp-attribute-fidelity", IPP_TAG_ZERO)) != NULL) { if (ippGetCount(attr) != 1 || ippGetValueTag(attr) != IPP_TAG_BOOLEAN) { respond_unsupported(client, attr); valid = false; } } fidelity = ippGetBoolean(attr, 0); if ((attr = ippFindAttribute(client->request, "copies", IPP_TAG_ZERO)) != NULL) { if (ippGetCount(attr) != 1 || ippGetValueTag(attr) != IPP_TAG_INTEGER || ippGetInteger(attr, 0) < 1 || ippGetInteger(attr, 0) > 999) { if (fidelity) { respond_unsupported(client, attr); valid = false; } else { respond_ignored(client, attr); ippDeleteAttribute(client->request, attr); } } } attr_col = ippFindAttribute(client->request, "finishings-col", IPP_TAG_ZERO); if ((attr = ippFindAttribute(client->request, "finishings", IPP_TAG_ZERO)) != NULL) { if (attr_col) { // Cannot specify both finishings and finishings-col... respond_unsupported(client, attr); respond_unsupported(client, attr_col); ippSetStatusCode(client->response, IPP_STATUS_ERROR_BAD_REQUEST); valid = false; } else if (ippGetValueTag(attr) != IPP_TAG_ENUM) { if (fidelity) { respond_unsupported(client, attr); valid = false; } else { respond_ignored(client, attr); ippDeleteAttribute(client->request, attr); } } else { supported = ippFindAttribute(client->printer->attrs, "finishings-supported", IPP_TAG_ENUM); for (i = 0, count = ippGetCount(attr); i < count; i ++) { if (!ippContainsInteger(supported, ippGetInteger(attr, i))) break; } if (i < count) { if (fidelity) { respond_unsupported(client, attr); valid = false; } else { respond_ignored(client, attr); ippDeleteAttribute(client->request, attr); } } } } else if (attr_col) { if (ippGetValueTag(attr_col) != IPP_TAG_BEGIN_COLLECTION) { if (fidelity) { respond_unsupported(client, attr_col); valid = false; } else { respond_ignored(client, attr_col); ippDeleteAttribute(client->request, attr_col); } } else { supported = ippFindAttribute(client->printer->attrs, "finishing-template-supported", IPP_TAG_ZERO); for (i = 0, count = ippGetCount(attr_col); i < count; i ++) { if (!ippContainsString(supported, ippGetString(ippFindAttribute(ippGetCollection(attr_col, i), "finishing-template", IPP_TAG_ZERO), 0, NULL))) break; } if (i < count) { if (fidelity) { respond_unsupported(client, attr_col); valid = false; } else { respond_ignored(client, attr_col); ippDeleteAttribute(client->request, attr_col); } } } } if ((attr = ippFindAttribute(client->request, "job-hold-until", IPP_TAG_ZERO)) != NULL) { if (ippGetCount(attr) != 1 || (ippGetValueTag(attr) != IPP_TAG_NAME && ippGetValueTag(attr) != IPP_TAG_NAMELANG && ippGetValueTag(attr) != IPP_TAG_KEYWORD) || strcmp(ippGetString(attr, 0, NULL), "no-hold")) { respond_unsupported(client, attr); valid = false; } } if ((attr = ippFindAttribute(client->request, "job-impressions", IPP_TAG_ZERO)) != NULL) { if (ippGetCount(attr) != 1 || ippGetValueTag(attr) != IPP_TAG_INTEGER || ippGetInteger(attr, 0) < 0) { respond_unsupported(client, attr); valid = false; } } if ((attr = ippFindAttribute(client->request, "job-name", IPP_TAG_ZERO)) != NULL) { if (ippGetCount(attr) != 1 || (ippGetValueTag(attr) != IPP_TAG_NAME && ippGetValueTag(attr) != IPP_TAG_NAMELANG)) { respond_unsupported(client, attr); valid = false; } ippSetGroupTag(client->request, &attr, IPP_TAG_JOB); } else { ippAddString(client->request, IPP_TAG_JOB, IPP_TAG_NAME, "job-name", NULL, "Untitled"); } if ((attr = ippFindAttribute(client->request, "job-priority", IPP_TAG_ZERO)) != NULL) { if (ippGetCount(attr) != 1 || ippGetValueTag(attr) != IPP_TAG_INTEGER || ippGetInteger(attr, 0) < 1 || ippGetInteger(attr, 0) > 100) { respond_unsupported(client, attr); valid = false; } } if ((attr = ippFindAttribute(client->request, "job-sheets", IPP_TAG_ZERO)) != NULL) { if (ippGetCount(attr) != 1 || (ippGetValueTag(attr) != IPP_TAG_NAME && ippGetValueTag(attr) != IPP_TAG_NAMELANG && ippGetValueTag(attr) != IPP_TAG_KEYWORD) || strcmp(ippGetString(attr, 0, NULL), "none")) { respond_unsupported(client, attr); valid = false; } } attr_col = ippFindAttribute(client->request, "media-col", IPP_TAG_ZERO); if ((attr = ippFindAttribute(client->request, "media", IPP_TAG_ZERO)) != NULL) { if (attr_col) { // Cannot send both media and media-col in the same request, this is a // bad request... respond_unsupported(client, attr); respond_unsupported(client, attr_col); ippSetStatusCode(client->response, IPP_STATUS_ERROR_BAD_REQUEST); valid = false; } else if (ippGetCount(attr) != 1 || (ippGetValueTag(attr) != IPP_TAG_NAME && ippGetValueTag(attr) != IPP_TAG_NAMELANG && ippGetValueTag(attr) != IPP_TAG_KEYWORD)) { respond_unsupported(client, attr); valid = false; } else { supported = ippFindAttribute(client->printer->attrs, "media-supported", IPP_TAG_KEYWORD); if (!ippContainsString(supported, ippGetString(attr, 0, NULL))) { if (fidelity) { respond_unsupported(client, attr); valid = false; } else { respond_ignored(client, attr); ippDeleteAttribute(client->request, attr); } } } } else if (attr_col) { ipp_t *col, // media-col collection *size; // media-size collection ipp_attribute_t *member, // Member attribute *size_name, // "media-size-name" member attribute *x_dim, // x-dimension *y_dim; // y-dimension int x_value, // y-dimension value y_value; // x-dimension value if (ippGetCount(attr_col) != 1 || ippGetValueTag(attr_col) != IPP_TAG_BEGIN_COLLECTION) { respond_unsupported(client, attr_col); valid = false; } col = ippGetCollection(attr_col, 0); member = ippFindAttribute(col, "media-size", IPP_TAG_BEGIN_COLLECTION); if ((size_name = ippFindAttribute(col, "media-size-name", IPP_TAG_ZERO)) != NULL) { if (member) { // Cannot specify both media-size and media-size-name... respond_unsupported(client, attr_col); ippSetStatusCode(client->response, IPP_STATUS_ERROR_BAD_REQUEST); valid = false; } else if (ippGetCount(size_name) != 1 || (ippGetValueTag(size_name) != IPP_TAG_NAME && ippGetValueTag(size_name) != IPP_TAG_NAMELANG && ippGetValueTag(size_name) != IPP_TAG_KEYWORD)) { respond_unsupported(client, attr_col); valid = false; } else { supported = ippFindAttribute(client->printer->attrs, "media-supported", IPP_TAG_KEYWORD); if (!ippContainsString(supported, ippGetString(size_name, 0, NULL))) { if (fidelity) { respond_unsupported(client, attr_col); valid = false; } else { respond_ignored(client, attr_col); ippDeleteAttribute(client->request, attr_col); } } } } else if (member) { if (ippGetCount(member) != 1) { respond_unsupported(client, attr_col); valid = false; } else { size = ippGetCollection(member, 0); if ((x_dim = ippFindAttribute(size, "x-dimension", IPP_TAG_INTEGER)) == NULL || ippGetCount(x_dim) != 1 || (y_dim = ippFindAttribute(size, "y-dimension", IPP_TAG_INTEGER)) == NULL || ippGetCount(y_dim) != 1) { respond_unsupported(client, attr_col); valid = false; } else { x_value = ippGetInteger(x_dim, 0); y_value = ippGetInteger(y_dim, 0); supported = ippFindAttribute(client->printer->attrs, "media-size-supported", IPP_TAG_BEGIN_COLLECTION); count = ippGetCount(supported); for (i = 0; i < count ; i ++) { int x_min, x_max; // Min/max width int y_min, y_max; // Min/max length size = ippGetCollection(supported, i); x_dim = ippFindAttribute(size, "x-dimension", IPP_TAG_ZERO); y_dim = ippFindAttribute(size, "y-dimension", IPP_TAG_ZERO); if (ippGetValueTag(x_dim) == IPP_TAG_INTEGER) { x_min = ippGetInteger(x_dim, 0) - 100; x_max = ippGetInteger(x_dim, 0) + 100; } else { x_min = ippGetRange(x_dim, 0, &x_max); } if (ippGetValueTag(y_dim) == IPP_TAG_INTEGER) { y_min = ippGetInteger(y_dim, 0) - 100; y_max = ippGetInteger(y_dim, 0) + 100; } else { y_min = ippGetRange(y_dim, 0, &y_max); } if ((x_value < x_min || x_value > x_max) && (y_value < y_min || y_value > y_max)) break; } if (i >= count) { if (fidelity) { respond_unsupported(client, attr_col); valid = false; } else { respond_ignored(client, attr_col); ippDeleteAttribute(client->request, attr_col); } } } } } } if ((attr = ippFindAttribute(client->request, "multiple-document-handling", IPP_TAG_ZERO)) != NULL) { if (ippGetCount(attr) != 1 || ippGetValueTag(attr) != IPP_TAG_KEYWORD || (strcmp(ippGetString(attr, 0, NULL), "separate-documents-uncollated-copies") && strcmp(ippGetString(attr, 0, NULL), "separate-documents-collated-copies"))) { respond_unsupported(client, attr); valid = false; } } if ((attr = ippFindAttribute(client->request, "orientation-requested", IPP_TAG_ZERO)) != NULL) { if (ippGetCount(attr) != 1 || ippGetValueTag(attr) != IPP_TAG_ENUM || ippGetInteger(attr, 0) < IPP_ORIENT_PORTRAIT || ippGetInteger(attr, 0) > IPP_ORIENT_REVERSE_PORTRAIT) { respond_unsupported(client, attr); valid = false; } } if ((attr = ippFindAttribute(client->request, "page-ranges", IPP_TAG_ZERO)) != NULL) { if (ippGetValueTag(attr) != IPP_TAG_RANGE) { respond_unsupported(client, attr); valid = false; } } if ((attr = ippFindAttribute(client->request, "print-quality", IPP_TAG_ZERO)) != NULL) { if (ippGetCount(attr) != 1 || ippGetValueTag(attr) != IPP_TAG_ENUM || ippGetInteger(attr, 0) < IPP_QUALITY_DRAFT || ippGetInteger(attr, 0) > IPP_QUALITY_HIGH) { respond_unsupported(client, attr); valid = false; } } if ((attr = ippFindAttribute(client->request, "printer-resolution", IPP_TAG_ZERO)) != NULL) { supported = ippFindAttribute(client->printer->attrs, "printer-resolution-supported", IPP_TAG_RESOLUTION); if (ippGetCount(attr) != 1 || ippGetValueTag(attr) != IPP_TAG_RESOLUTION || !supported) { respond_unsupported(client, attr); valid = false; } else { int xdpi, // Horizontal resolution for job template attribute ydpi, // Vertical resolution for job template attribute sydpi; // Vertical resolution for supported value ipp_res_t units, // Units for job template attribute sunits; // Units for supported value xdpi = ippGetResolution(attr, 0, &ydpi, &units); count = ippGetCount(supported); for (i = 0; i < count; i ++) { if (xdpi == ippGetResolution(supported, i, &sydpi, &sunits) && ydpi == sydpi && units == sunits) break; } if (i >= count) { if (fidelity) { respond_unsupported(client, attr); valid = false; } else { respond_ignored(client, attr); ippDeleteAttribute(client->request, attr); } } } } if ((attr = ippFindAttribute(client->request, "sides", IPP_TAG_ZERO)) != NULL) { const char *sides = ippGetString(attr, 0, NULL); // "sides" value... if (ippGetCount(attr) != 1 || ippGetValueTag(attr) != IPP_TAG_KEYWORD) { respond_unsupported(client, attr); valid = false; } else if ((supported = ippFindAttribute(client->printer->attrs, "sides-supported", IPP_TAG_KEYWORD)) != NULL) { if (!ippContainsString(supported, sides)) { if (fidelity) { respond_unsupported(client, attr); valid = false; } else { respond_ignored(client, attr); ippDeleteAttribute(client->request, attr); } } } else if (strcmp(sides, "one-sided")) { if (fidelity) { respond_unsupported(client, attr); valid = false; } else { respond_ignored(client, attr); ippDeleteAttribute(client->request, attr); } } } return (valid); }