/* * Line Printer Daemon interface for CUPS. * * Copyright 2007-2016 by Apple Inc. * Copyright 1997-2006 by Easy Software Products, all rights reserved. * * Licensed under Apache License v2.0. See the file "LICENSE" for more information. */ /* * Include necessary headers... */ #define _CUPS_NO_DEPRECATED #include #include #include #include #include #include #include #include #ifdef HAVE_INTTYPES_H # include #endif /* HAVE_INTTYPES_H */ #ifdef __APPLE__ # include #endif /* __APPLE__ */ /* * LPD "mini-daemon" for CUPS. This program must be used in conjunction * with inetd or another similar program that monitors ports and starts * daemons for each client connection. A typical configuration is: * * printer stream tcp nowait lp /usr/lib/cups/daemon/cups-lpd cups-lpd * * This daemon implements most of RFC 1179 (the unofficial LPD specification) * except for: * * - This daemon does not check to make sure that the source port is * between 721 and 731, since it isn't necessary for proper * functioning and port-based security is no security at all! * * - The "Print any waiting jobs" command is a no-op. * * The LPD-to-IPP mapping is as defined in RFC 2569. The report formats * currently match the Solaris LPD mini-daemon. */ /* * Prototypes... */ static int create_job(http_t *http, const char *dest, const char *title, const char *user, int num_options, cups_option_t *options); static int get_printer(http_t *http, const char *name, char *dest, size_t destsize, cups_option_t **options, int *accepting, int *shared, ipp_pstate_t *state); static int print_file(http_t *http, int id, const char *filename, const char *docname, const char *user, const char *format, int last); static int recv_print_job(const char *name, int num_defaults, cups_option_t *defaults); static int remove_jobs(const char *name, const char *agent, const char *list); static int send_state(const char *name, const char *list, int longstatus); static char *smart_gets(char *s, int len, FILE *fp); static void smart_strlcpy(char *dst, const char *src, size_t dstsize); /* * 'main()' - Process an incoming LPD request... */ int /* O - Exit status */ main(int argc, /* I - Number of command-line arguments */ char *argv[]) /* I - Command-line arguments */ { int i; /* Looping var */ int num_defaults; /* Number of default options */ cups_option_t *defaults; /* Default options */ char line[256], /* Command string */ command, /* Command code */ *dest, /* Pointer to destination */ *list, /* Pointer to list */ *agent, /* Pointer to user */ status; /* Status for client */ socklen_t hostlen; /* Size of client address */ http_addr_t hostaddr; /* Address of client */ char hostname[256], /* Name of client */ hostip[256], /* IP address */ *hostfamily; /* Address family */ int hostlookups; /* Do hostname lookups? */ #ifdef __APPLE__ xpc_transaction_begin(); #endif /* __APPLE__ */ /* * Don't buffer the output... */ setbuf(stdout, NULL); /* * Log things using the "cups-lpd" name... */ openlog("cups-lpd", LOG_PID, LOG_LPR); /* * Scan the command-line for options... */ num_defaults = 0; defaults = NULL; hostlookups = 1; for (i = 1; i < argc; i ++) if (argv[i][0] == '-') { switch (argv[i][1]) { case 'h' : /* -h hostname[:port] */ if (argv[i][2]) cupsSetServer(argv[i] + 2); else { i ++; if (i < argc) cupsSetServer(argv[i]); else syslog(LOG_WARNING, "Expected hostname string after -h option!"); } break; case 'o' : /* Option */ if (argv[i][2]) num_defaults = cupsParseOptions(argv[i] + 2, num_defaults, &defaults); else { i ++; if (i < argc) num_defaults = cupsParseOptions(argv[i], num_defaults, &defaults); else syslog(LOG_WARNING, "Expected option string after -o option!"); } break; case 'n' : /* Don't do hostname lookups */ hostlookups = 0; break; default : syslog(LOG_WARNING, "Unknown option \"%c\" ignored!", argv[i][1]); break; } } else syslog(LOG_WARNING, "Unknown command-line option \"%s\" ignored!", argv[i]); /* * Get the address of the client... */ hostlen = sizeof(hostaddr); if (getpeername(0, (struct sockaddr *)&hostaddr, &hostlen)) { syslog(LOG_WARNING, "Unable to get client address - %s", strerror(errno)); strlcpy(hostname, "unknown", sizeof(hostname)); } else { httpAddrString(&hostaddr, hostip, sizeof(hostip)); if (hostlookups) httpAddrLookup(&hostaddr, hostname, sizeof(hostname)); else strlcpy(hostname, hostip, sizeof(hostname)); #ifdef AF_INET6 if (hostaddr.addr.sa_family == AF_INET6) hostfamily = "IPv6"; else #endif /* AF_INET6 */ hostfamily = "IPv4"; syslog(LOG_INFO, "Connection from %s (%s %s)", hostname, hostfamily, hostip); } num_defaults = cupsAddOption("job-originating-host-name", hostname, num_defaults, &defaults); /* * RFC1179 specifies that only 1 daemon command can be received for * every connection. */ if (smart_gets(line, sizeof(line), stdin) == NULL) { /* * Unable to get command from client! Send an error status and return. */ syslog(LOG_ERR, "Unable to get command line from client!"); putchar(1); #ifdef __APPLE__ xpc_transaction_end(); #endif /* __APPLE__ */ return (1); } /* * The first byte is the command byte. After that will be the queue name, * resource list, and/or user name. */ if ((command = line[0]) == '\0') dest = line; else dest = line + 1; if (command == 0x02) list = NULL; else { for (list = dest; *list && !isspace(*list & 255); list ++); while (isspace(*list & 255)) *list++ = '\0'; } /* * Do the command... */ switch (command) { default : /* Unknown command */ syslog(LOG_ERR, "Unknown LPD command 0x%02X!", command); syslog(LOG_ERR, "Command line = %s", line + 1); putchar(1); status = 1; break; case 0x01 : /* Print any waiting jobs */ syslog(LOG_INFO, "Print waiting jobs (no-op)"); putchar(0); status = 0; break; case 0x02 : /* Receive a printer job */ syslog(LOG_INFO, "Receive print job for %s", dest); /* recv_print_job() sends initial status byte */ status = (char)recv_print_job(dest, num_defaults, defaults); break; case 0x03 : /* Send queue state (short) */ syslog(LOG_INFO, "Send queue state (short) for %s %s", dest, list); /* no status byte for this command */ status = (char)send_state(dest, list, 0); break; case 0x04 : /* Send queue state (long) */ syslog(LOG_INFO, "Send queue state (long) for %s %s", dest, list); /* no status byte for this command */ status = (char)send_state(dest, list, 1); break; case 0x05 : /* Remove jobs */ if (list) { /* * Grab the agent and skip to the list of users and/or jobs. */ agent = list; for (; *list && !isspace(*list & 255); list ++); while (isspace(*list & 255)) *list++ = '\0'; syslog(LOG_INFO, "Remove jobs %s on %s by %s", list, dest, agent); status = (char)remove_jobs(dest, agent, list); } else status = 1; putchar(status); break; } syslog(LOG_INFO, "Closing connection"); closelog(); #ifdef __APPLE__ xpc_transaction_end(); #endif /* __APPLE__ */ return (status); } /* * 'create_job()' - Create a new print job. */ static int /* O - Job ID or -1 on error */ create_job(http_t *http, /* I - HTTP connection */ const char *dest, /* I - Destination name */ const char *title, /* I - job-name */ const char *user, /* I - requesting-user-name */ int num_options, /* I - Number of options for job */ cups_option_t *options) /* I - Options for job */ { ipp_t *request; /* IPP request */ ipp_t *response; /* IPP response */ ipp_attribute_t *attr; /* IPP attribute */ char uri[HTTP_MAX_URI]; /* Printer URI */ int id; /* Job ID */ /* * Setup the Create-Job request... */ request = ippNewRequest(IPP_OP_CREATE_JOB); httpAssembleURIf(HTTP_URI_CODING_ALL, uri, sizeof(uri), "ipp", NULL, "localhost", 0, "/printers/%s", dest); ippAddString(request, IPP_TAG_OPERATION, IPP_TAG_URI, "printer-uri", NULL, uri); ippAddString(request, IPP_TAG_OPERATION, IPP_TAG_NAME, "requesting-user-name", NULL, user); if (title[0]) ippAddString(request, IPP_TAG_OPERATION, IPP_TAG_NAME, "job-name", NULL, title); cupsEncodeOptions(request, num_options, options); /* * Do the request... */ snprintf(uri, sizeof(uri), "/printers/%s", dest); response = cupsDoRequest(http, request, uri); if (!response || cupsLastError() > IPP_STATUS_OK_CONFLICTING) { syslog(LOG_ERR, "Unable to create job - %s", cupsLastErrorString()); ippDelete(response); return (-1); } /* * Get the job-id value from the response and return it... */ if ((attr = ippFindAttribute(response, "job-id", IPP_TAG_INTEGER)) == NULL) { id = -1; syslog(LOG_ERR, "No job-id attribute found in response from server!"); } else { id = attr->values[0].integer; syslog(LOG_INFO, "Print file - job ID = %d", id); } ippDelete(response); return (id); } /* * 'get_printer()' - Get the named printer and its options. */ static int /* O - Number of options or -1 on error */ get_printer(http_t *http, /* I - HTTP connection */ const char *name, /* I - Printer name from request */ char *dest, /* I - Destination buffer */ size_t destsize, /* I - Size of destination buffer */ cups_option_t **options, /* O - Printer options */ int *accepting, /* O - printer-is-accepting-jobs value */ int *shared, /* O - printer-is-shared value */ ipp_pstate_t *state) /* O - printer-state value */ { int num_options; /* Number of options */ cups_file_t *fp; /* lpoptions file */ char line[1024], /* Line from lpoptions file */ *value, /* Pointer to value on line */ *optptr; /* Pointer to options on line */ int linenum; /* Line number in file */ const char *cups_serverroot; /* CUPS_SERVERROOT env var */ ipp_t *request; /* IPP request */ ipp_t *response; /* IPP response */ ipp_attribute_t *attr; /* IPP attribute */ char uri[HTTP_MAX_URI]; /* Printer URI */ static const char * const requested[] = { /* Requested attributes */ "printer-info", "printer-is-accepting-jobs", "printer-is-shared", "printer-name", "printer-state" }; /* * Initialize everything... */ if (accepting) *accepting = 0; if (shared) *shared = 0; if (state) *state = IPP_PSTATE_STOPPED; if (options) *options = NULL; /* * See if the name is a queue name optionally with an instance name. */ strlcpy(dest, name, destsize); if ((value = strchr(dest, '/')) != NULL) *value = '\0'; /* * Setup the Get-Printer-Attributes request... */ request = ippNewRequest(IPP_OP_GET_PRINTER_ATTRIBUTES); httpAssembleURIf(HTTP_URI_CODING_ALL, uri, sizeof(uri), "ipp", NULL, "localhost", 0, "/printers/%s", dest); ippAddString(request, IPP_TAG_OPERATION, IPP_TAG_URI, "printer-uri", NULL, uri); ippAddStrings(request, IPP_TAG_OPERATION, IPP_TAG_KEYWORD, "requested-attributes", (int)(sizeof(requested) / sizeof(requested[0])), NULL, requested); /* * Do the request... */ response = cupsDoRequest(http, request, "/"); if (!response || cupsLastError() > IPP_STATUS_OK_CONFLICTING) { /* * If we can't find the printer by name, look up the printer-name * using the printer-info values... */ ipp_attribute_t *accepting_attr,/* printer-is-accepting-jobs */ *info_attr, /* printer-info */ *name_attr, /* printer-name */ *shared_attr, /* printer-is-shared */ *state_attr; /* printer-state */ ippDelete(response); /* * Setup the CUPS-Get-Printers request... */ request = ippNewRequest(IPP_OP_CUPS_GET_PRINTERS); ippAddStrings(request, IPP_TAG_OPERATION, IPP_TAG_KEYWORD, "requested-attributes", (int)(sizeof(requested) / sizeof(requested[0])), NULL, requested); /* * Do the request... */ response = cupsDoRequest(http, request, "/"); if (!response || cupsLastError() > IPP_STATUS_OK_CONFLICTING) { syslog(LOG_ERR, "Unable to get list of printers - %s", cupsLastErrorString()); ippDelete(response); return (-1); } /* * Scan the response for printers... */ *dest = '\0'; attr = response->attrs; while (attr) { /* * Skip to the next printer... */ while (attr && attr->group_tag != IPP_TAG_PRINTER) attr = attr->next; if (!attr) break; /* * Get all of the attributes for the current printer... */ accepting_attr = NULL; info_attr = NULL; name_attr = NULL; shared_attr = NULL; state_attr = NULL; while (attr && attr->group_tag == IPP_TAG_PRINTER) { if (!strcmp(attr->name, "printer-is-accepting-jobs") && attr->value_tag == IPP_TAG_BOOLEAN) accepting_attr = attr; else if (!strcmp(attr->name, "printer-info") && attr->value_tag == IPP_TAG_TEXT) info_attr = attr; else if (!strcmp(attr->name, "printer-name") && attr->value_tag == IPP_TAG_NAME) name_attr = attr; else if (!strcmp(attr->name, "printer-is-shared") && attr->value_tag == IPP_TAG_BOOLEAN) shared_attr = attr; else if (!strcmp(attr->name, "printer-state") && attr->value_tag == IPP_TAG_ENUM) state_attr = attr; attr = attr->next; } if (info_attr && name_attr && !_cups_strcasecmp(name, info_attr->values[0].string.text)) { /* * Found a match, use this one! */ strlcpy(dest, name_attr->values[0].string.text, destsize); if (accepting && accepting_attr) *accepting = accepting_attr->values[0].boolean; if (shared && shared_attr) *shared = shared_attr->values[0].boolean; if (state && state_attr) *state = (ipp_pstate_t)state_attr->values[0].integer; break; } } ippDelete(response); if (!*dest) { syslog(LOG_ERR, "Unable to find \"%s\" in list of printers!", name); return (-1); } name = dest; } else { /* * Get values from the response... */ if (accepting) { if ((attr = ippFindAttribute(response, "printer-is-accepting-jobs", IPP_TAG_BOOLEAN)) == NULL) syslog(LOG_ERR, "No printer-is-accepting-jobs attribute found in " "response from server!"); else *accepting = attr->values[0].boolean; } if (shared) { if ((attr = ippFindAttribute(response, "printer-is-shared", IPP_TAG_BOOLEAN)) == NULL) { syslog(LOG_ERR, "No printer-is-shared attribute found in " "response from server!"); *shared = 1; } else *shared = attr->values[0].boolean; } if (state) { if ((attr = ippFindAttribute(response, "printer-state", IPP_TAG_ENUM)) == NULL) syslog(LOG_ERR, "No printer-state attribute found in " "response from server!"); else *state = (ipp_pstate_t)attr->values[0].integer; } ippDelete(response); } /* * Next look for the printer in the lpoptions file... */ num_options = 0; if (options && shared && accepting) { if ((cups_serverroot = getenv("CUPS_SERVERROOT")) == NULL) cups_serverroot = CUPS_SERVERROOT; snprintf(line, sizeof(line), "%s/lpoptions", cups_serverroot); if ((fp = cupsFileOpen(line, "r")) != NULL) { linenum = 0; while (cupsFileGetConf(fp, line, sizeof(line), &value, &linenum)) { /* * Make sure we have "Dest name options" or "Default name options"... */ if ((_cups_strcasecmp(line, "Dest") && _cups_strcasecmp(line, "Default")) || !value) continue; /* * Separate destination name from options... */ for (optptr = value; *optptr && !isspace(*optptr & 255); optptr ++); while (*optptr == ' ') *optptr++ = '\0'; /* * If this is our destination, parse the options and break out of * the loop - we're done! */ if (!_cups_strcasecmp(value, name)) { num_options = cupsParseOptions(optptr, num_options, options); break; } } cupsFileClose(fp); } } else if (options) *options = NULL; /* * Return the number of options for this destination... */ return (num_options); } /* * 'print_file()' - Add a file to the current job. */ static int /* O - 0 on success, -1 on failure */ print_file(http_t *http, /* I - HTTP connection */ int id, /* I - Job ID */ const char *filename, /* I - File to print */ const char *docname, /* I - document-name */ const char *user, /* I - requesting-user-name */ const char *format, /* I - document-format */ int last) /* I - 1 = last file in job */ { ipp_t *request; /* IPP request */ char uri[HTTP_MAX_URI]; /* Printer URI */ /* * Setup the Send-Document request... */ request = ippNewRequest(IPP_OP_SEND_DOCUMENT); snprintf(uri, sizeof(uri), "ipp://localhost/jobs/%d", id); ippAddString(request, IPP_TAG_OPERATION, IPP_TAG_URI, "job-uri", NULL, uri); ippAddString(request, IPP_TAG_OPERATION, IPP_TAG_NAME, "requesting-user-name", NULL, user); if (docname) ippAddString(request, IPP_TAG_OPERATION, IPP_TAG_NAME, "document-name", NULL, docname); if (format) ippAddString(request, IPP_TAG_OPERATION, IPP_TAG_MIMETYPE, "document-format", NULL, format); ippAddBoolean(request, IPP_TAG_OPERATION, "last-document", (char)last); /* * Do the request... */ snprintf(uri, sizeof(uri), "/jobs/%d", id); ippDelete(cupsDoFileRequest(http, request, uri, filename)); if (cupsLastError() > IPP_STATUS_OK_CONFLICTING) { syslog(LOG_ERR, "Unable to send document - %s", cupsLastErrorString()); return (-1); } return (0); } /* * 'recv_print_job()' - Receive a print job from the client. */ static int /* O - Command status */ recv_print_job( const char *queue, /* I - Printer name */ int num_defaults, /* I - Number of default options */ cups_option_t *defaults) /* I - Default options */ { http_t *http; /* HTTP connection */ int i; /* Looping var */ int status; /* Command status */ int fd; /* Temporary file */ FILE *fp; /* File pointer */ char filename[1024]; /* Temporary filename */ ssize_t bytes; /* Bytes received */ size_t total; /* Total bytes */ char line[256], /* Line from file/stdin */ command, /* Command from line */ *count, /* Number of bytes */ *name; /* Name of file */ const char *job_sheets; /* Job sheets */ int num_data; /* Number of data files */ char control[1024], /* Control filename */ data[100][256], /* Data files */ temp[100][1024]; /* Temporary files */ char user[1024], /* User name */ title[1024], /* Job title */ docname[1024], /* Document name */ dest[256]; /* Printer/class queue */ int accepting, /* printer-is-accepting */ shared, /* printer-is-shared */ num_options; /* Number of options */ cups_option_t *options; /* Options */ int id; /* Job ID */ int docnumber, /* Current document number */ doccount; /* Count of documents */ /* * Connect to the server... */ http = httpConnect2(cupsServer(), ippPort(), NULL, AF_UNSPEC, cupsEncryption(), 1, 30000, NULL); if (!http) { syslog(LOG_ERR, "Unable to connect to server: %s", strerror(errno)); putchar(1); return (1); } /* * See if the printer is available... */ num_options = get_printer(http, queue, dest, sizeof(dest), &options, &accepting, &shared, NULL); if (num_options < 0 || !accepting || !shared) { if (dest[0]) syslog(LOG_INFO, "Rejecting job because \"%s\" is not %s", dest, !accepting ? "accepting jobs" : "shared"); else syslog(LOG_ERR, "Unable to get printer information for \"%s\"", queue); httpClose(http); putchar(1); return (1); } putchar(0); /* OK so far... */ /* * Read the request... */ status = 0; num_data = 0; fd = -1; control[0] = '\0'; while (smart_gets(line, sizeof(line), stdin) != NULL) { if (strlen(line) < 2) { status = 1; break; } command = line[0]; count = line + 1; for (name = count + 1; *name && !isspace(*name & 255); name ++); while (isspace(*name & 255)) *name++ = '\0'; switch (command) { default : case 0x01 : /* Abort */ status = 1; break; case 0x02 : /* Receive control file */ if (strlen(name) < 2) { syslog(LOG_ERR, "Bad control file name \"%s\"", name); putchar(1); status = 1; break; } if (control[0]) { /* * Append to the existing control file - the LPD spec is * not entirely clear, but at least the OS/2 LPD code sends * multiple control files per connection... */ if ((fd = open(control, O_WRONLY)) < 0) { syslog(LOG_ERR, "Unable to append to temporary control file \"%s\" - %s", control, strerror(errno)); putchar(1); status = 1; break; } lseek(fd, 0, SEEK_END); } else { if ((fd = cupsTempFd(control, sizeof(control))) < 0) { syslog(LOG_ERR, "Unable to open temporary control file \"%s\" - %s", control, strerror(errno)); putchar(1); status = 1; break; } strlcpy(filename, control, sizeof(filename)); } break; case 0x03 : /* Receive data file */ if (strlen(name) < 2) { syslog(LOG_ERR, "Bad data file name \"%s\"", name); putchar(1); status = 1; break; } if (num_data >= (int)(sizeof(data) / sizeof(data[0]))) { /* * Too many data files... */ syslog(LOG_ERR, "Too many data files (%d)", num_data); putchar(1); status = 1; break; } strlcpy(data[num_data], name, sizeof(data[0])); if ((fd = cupsTempFd(temp[num_data], sizeof(temp[0]))) < 0) { syslog(LOG_ERR, "Unable to open temporary data file \"%s\" - %s", temp[num_data], strerror(errno)); putchar(1); status = 1; break; } strlcpy(filename, temp[num_data], sizeof(filename)); num_data ++; break; } putchar(status); if (status) break; /* * Copy the data or control file from the client... */ for (total = (size_t)strtoll(count, NULL, 10); total > 0; total -= (size_t)bytes) { if (total > sizeof(line)) bytes = (ssize_t)sizeof(line); else bytes = (ssize_t)total; if ((bytes = (ssize_t)fread(line, 1, (size_t)bytes, stdin)) > 0) bytes = write(fd, line, (size_t)bytes); if (bytes < 1) { syslog(LOG_ERR, "Error while reading file - %s", strerror(errno)); status = 1; break; } } /* * Read trailing nul... */ if (!status) { if (fread(line, 1, 1, stdin) < 1) { status = 1; syslog(LOG_ERR, "Error while reading trailing nul - %s", strerror(errno)); } else if (line[0]) { status = 1; syslog(LOG_ERR, "Trailing character after file is not nul (%02X)!", line[0]); } } /* * Close the file and send an acknowledgement... */ close(fd); putchar(status); if (status) break; } if (!status) { /* * Process the control file and print stuff... */ if ((fp = fopen(control, "rb")) == NULL) status = 1; else { /* * Copy the default options... */ for (i = 0; i < num_defaults; i ++) num_options = cupsAddOption(defaults[i].name, defaults[i].value, num_options, &options); /* * Grab the job information... */ title[0] = '\0'; user[0] = '\0'; docname[0] = '\0'; doccount = 0; while (smart_gets(line, sizeof(line), fp) != NULL) { /* * Process control lines... */ switch (line[0]) { case 'J' : /* Job name */ smart_strlcpy(title, line + 1, sizeof(title)); break; case 'N' : /* Document name */ smart_strlcpy(docname, line + 1, sizeof(docname)); break; case 'P' : /* User identification */ smart_strlcpy(user, line + 1, sizeof(user)); break; case 'L' : /* Print banner page */ /* * If a banner was requested and it's not overridden by a * command line option and the destination's default is none * then add the standard banner... */ if (cupsGetOption("job-sheets", num_defaults, defaults) == NULL && ((job_sheets = cupsGetOption("job-sheets", num_options, options)) == NULL || !strcmp(job_sheets, "none,none"))) { num_options = cupsAddOption("job-sheets", "standard", num_options, &options); } break; case 'c' : /* Plot CIF file */ case 'd' : /* Print DVI file */ case 'f' : /* Print formatted file */ case 'g' : /* Plot file */ case 'l' : /* Print file leaving control characters (raw) */ case 'n' : /* Print ditroff output file */ case 'o' : /* Print PostScript output file */ case 'p' : /* Print file with 'pr' format (prettyprint) */ case 'r' : /* File to print with FORTRAN carriage control */ case 't' : /* Print troff output file */ case 'v' : /* Print raster file */ doccount ++; if (line[0] == 'l' && !cupsGetOption("document-format", num_options, options)) num_options = cupsAddOption("raw", "", num_options, &options); if (line[0] == 'p') num_options = cupsAddOption("prettyprint", "", num_options, &options); break; } if (status) break; } /* * Check that we have a username... */ if (!user[0]) { syslog(LOG_WARNING, "No username specified by client! " "Using \"anonymous\"..."); strlcpy(user, "anonymous", sizeof(user)); } /* * Create the job... */ if ((id = create_job(http, dest, title, user, num_options, options)) < 0) status = 1; else { /* * Then print the job files... */ rewind(fp); docname[0] = '\0'; docnumber = 0; while (smart_gets(line, sizeof(line), fp) != NULL) { /* * Process control lines... */ switch (line[0]) { case 'N' : /* Document name */ smart_strlcpy(docname, line + 1, sizeof(docname)); break; case 'c' : /* Plot CIF file */ case 'd' : /* Print DVI file */ case 'f' : /* Print formatted file */ case 'g' : /* Plot file */ case 'l' : /* Print file leaving control characters (raw) */ case 'n' : /* Print ditroff output file */ case 'o' : /* Print PostScript output file */ case 'p' : /* Print file with 'pr' format (prettyprint) */ case 'r' : /* File to print with FORTRAN carriage control */ case 't' : /* Print troff output file */ case 'v' : /* Print raster file */ /* * Figure out which file we are printing... */ for (i = 0; i < num_data; i ++) if (!strcmp(data[i], line + 1)) break; if (i >= num_data) { status = 1; break; } /* * Send the print file... */ docnumber ++; if (print_file(http, id, temp[i], docname, user, cupsGetOption("document-format", num_options, options), docnumber == doccount)) status = 1; else status = 0; break; } if (status) break; } } fclose(fp); } } cupsFreeOptions(num_options, options); httpClose(http); /* * Clean up all temporary files and return... */ unlink(control); for (i = 0; i < num_data; i ++) unlink(temp[i]); return (status); } /* * 'remove_jobs()' - Cancel one or more jobs. */ static int /* O - Command status */ remove_jobs(const char *dest, /* I - Destination */ const char *agent, /* I - User agent */ const char *list) /* I - List of jobs or users */ { int id; /* Job ID */ http_t *http; /* HTTP server connection */ ipp_t *request; /* IPP Request */ char uri[HTTP_MAX_URI]; /* Job URI */ (void)dest; /* Suppress compiler warnings... */ /* * Try connecting to the local server... */ if ((http = httpConnect2(cupsServer(), ippPort(), NULL, AF_UNSPEC, cupsEncryption(), 1, 30000, NULL)) == NULL) { syslog(LOG_ERR, "Unable to connect to server %s: %s", cupsServer(), strerror(errno)); return (1); } /* * Loop for each job... */ while ((id = atoi(list)) > 0) { /* * Skip job ID in list... */ while (isdigit(*list & 255)) list ++; while (isspace(*list & 255)) list ++; /* * Build an IPP_OP_CANCEL_JOB request, which requires the following * attributes: * * attributes-charset * attributes-natural-language * job-uri * requesting-user-name */ request = ippNewRequest(IPP_OP_CANCEL_JOB); snprintf(uri, sizeof(uri), "ipp://localhost/jobs/%d", id); ippAddString(request, IPP_TAG_OPERATION, IPP_TAG_URI, "job-uri", NULL, uri); ippAddString(request, IPP_TAG_OPERATION, IPP_TAG_NAME, "requesting-user-name", NULL, agent); /* * Do the request and get back a response... */ ippDelete(cupsDoRequest(http, request, "/jobs")); if (cupsLastError() > IPP_STATUS_OK_CONFLICTING) { syslog(LOG_WARNING, "Cancel of job ID %d failed: %s\n", id, cupsLastErrorString()); httpClose(http); return (1); } else syslog(LOG_INFO, "Job ID %d canceled", id); } httpClose(http); return (0); } /* * 'send_state()' - Send the queue state. */ static int /* O - Command status */ send_state(const char *queue, /* I - Destination */ const char *list, /* I - Job or user */ int longstatus) /* I - List of jobs or users */ { int id; /* Job ID from list */ http_t *http; /* HTTP server connection */ ipp_t *request, /* IPP Request */ *response; /* IPP Response */ ipp_attribute_t *attr; /* Current attribute */ ipp_pstate_t state; /* Printer state */ const char *jobdest, /* Pointer into job-printer-uri */ *jobuser, /* Pointer to job-originating-user-name */ *jobname; /* Pointer to job-name */ ipp_jstate_t jobstate; /* job-state */ int jobid, /* job-id */ jobsize, /* job-k-octets */ jobcount, /* Number of jobs */ jobcopies, /* Number of copies */ rank; /* Rank of job */ char rankstr[255]; /* Rank string */ char namestr[1024]; /* Job name string */ char uri[HTTP_MAX_URI]; /* Printer URI */ char dest[256]; /* Printer/class queue */ static const char * const ranks[10] = /* Ranking strings */ { "th", "st", "nd", "rd", "th", "th", "th", "th", "th", "th" }; static const char * const requested[] = { /* Requested attributes */ "job-id", "job-k-octets", "job-state", "job-printer-uri", "job-originating-user-name", "job-name", "copies" }; /* * Try connecting to the local server... */ if ((http = httpConnect2(cupsServer(), ippPort(), NULL, AF_UNSPEC, cupsEncryption(), 1, 30000, NULL)) == NULL) { syslog(LOG_ERR, "Unable to connect to server %s: %s", cupsServer(), strerror(errno)); printf("Unable to connect to server %s: %s", cupsServer(), strerror(errno)); return (1); } /* * Get the actual destination name and printer state... */ if (get_printer(http, queue, dest, sizeof(dest), NULL, NULL, NULL, &state)) { syslog(LOG_ERR, "Unable to get printer %s: %s", queue, cupsLastErrorString()); printf("Unable to get printer %s: %s", queue, cupsLastErrorString()); return (1); } /* * Show the queue state... */ switch (state) { case IPP_PSTATE_IDLE : printf("%s is ready\n", dest); break; case IPP_PSTATE_PROCESSING : printf("%s is ready and printing\n", dest); break; case IPP_PSTATE_STOPPED : printf("%s is not ready\n", dest); break; } /* * Build an IPP_OP_GET_JOBS or IPP_OP_GET_JOB_ATTRIBUTES request, which requires * the following attributes: * * attributes-charset * attributes-natural-language * job-uri or printer-uri */ id = atoi(list); request = ippNewRequest(id ? IPP_OP_GET_JOB_ATTRIBUTES : IPP_OP_GET_JOBS); httpAssembleURIf(HTTP_URI_CODING_ALL, uri, sizeof(uri), "ipp", NULL, "localhost", 0, "/printers/%s", dest); ippAddString(request, IPP_TAG_OPERATION, IPP_TAG_URI, "printer-uri", NULL, uri); if (id) ippAddInteger(request, IPP_TAG_OPERATION, IPP_TAG_INTEGER, "job-id", id); else { ippAddString(request, IPP_TAG_OPERATION, IPP_TAG_NAME, "requesting-user-name", NULL, list); ippAddBoolean(request, IPP_TAG_OPERATION, "my-jobs", 1); } ippAddStrings(request, IPP_TAG_OPERATION, IPP_TAG_KEYWORD, "requested-attributes", sizeof(requested) / sizeof(requested[0]), NULL, requested); /* * Do the request and get back a response... */ jobcount = 0; response = cupsDoRequest(http, request, "/"); if (cupsLastError() > IPP_STATUS_OK_CONFLICTING) { printf("get-jobs failed: %s\n", cupsLastErrorString()); ippDelete(response); return (1); } /* * Loop through the job list and display them... */ for (attr = response->attrs, rank = 1; attr; attr = attr->next) { /* * Skip leading attributes until we hit a job... */ while (attr && (attr->group_tag != IPP_TAG_JOB || !attr->name)) attr = attr->next; if (!attr) break; /* * Pull the needed attributes from this job... */ jobid = 0; jobsize = 0; jobstate = IPP_JSTATE_PENDING; jobname = "untitled"; jobuser = NULL; jobdest = NULL; jobcopies = 1; while (attr && attr->group_tag == IPP_TAG_JOB) { if (!strcmp(attr->name, "job-id") && attr->value_tag == IPP_TAG_INTEGER) jobid = attr->values[0].integer; if (!strcmp(attr->name, "job-k-octets") && attr->value_tag == IPP_TAG_INTEGER) jobsize = attr->values[0].integer; if (!strcmp(attr->name, "job-state") && attr->value_tag == IPP_TAG_ENUM) jobstate = (ipp_jstate_t)attr->values[0].integer; if (!strcmp(attr->name, "job-printer-uri") && attr->value_tag == IPP_TAG_URI) if ((jobdest = strrchr(attr->values[0].string.text, '/')) != NULL) jobdest ++; if (!strcmp(attr->name, "job-originating-user-name") && attr->value_tag == IPP_TAG_NAME) jobuser = attr->values[0].string.text; if (!strcmp(attr->name, "job-name") && attr->value_tag == IPP_TAG_NAME) jobname = attr->values[0].string.text; if (!strcmp(attr->name, "copies") && attr->value_tag == IPP_TAG_INTEGER) jobcopies = attr->values[0].integer; attr = attr->next; } /* * See if we have everything needed... */ if (!jobdest || !jobid) { if (!attr) break; else continue; } if (!longstatus && jobcount == 0) puts("Rank Owner Job File(s) Total Size"); jobcount ++; /* * Display the job... */ if (jobstate == IPP_JSTATE_PROCESSING) strlcpy(rankstr, "active", sizeof(rankstr)); else { snprintf(rankstr, sizeof(rankstr), "%d%s", rank, ranks[rank % 10]); rank ++; } if (longstatus) { puts(""); if (jobcopies > 1) snprintf(namestr, sizeof(namestr), "%d copies of %s", jobcopies, jobname); else strlcpy(namestr, jobname, sizeof(namestr)); printf("%s: %-33.33s [job %d localhost]\n", jobuser, rankstr, jobid); printf(" %-39.39s %.0f bytes\n", namestr, 1024.0 * jobsize); } else printf("%-7s %-7.7s %-7d %-31.31s %.0f bytes\n", rankstr, jobuser, jobid, jobname, 1024.0 * jobsize); if (!attr) break; } ippDelete(response); if (jobcount == 0) puts("no entries"); httpClose(http); return (0); } /* * 'smart_gets()' - Get a line of text, removing the trailing CR and/or LF. */ static char * /* O - Line read or NULL */ smart_gets(char *s, /* I - Pointer to line buffer */ int len, /* I - Size of line buffer */ FILE *fp) /* I - File to read from */ { char *ptr, /* Pointer into line */ *end; /* End of line */ int ch; /* Character from file */ /* * Read the line; unlike fgets(), we read the entire line but dump * characters that go past the end of the buffer. Also, we accept * CR, LF, or CR LF for the line endings to be "safe", although * RFC 1179 specifically says "just use LF". */ ptr = s; end = s + len - 1; while ((ch = getc(fp)) != EOF) { if (ch == '\n') break; else if (ch == '\r') { /* * See if a LF follows... */ ch = getc(fp); if (ch != '\n') ungetc(ch, fp); break; } else if (ptr < end) *ptr++ = (char)ch; } *ptr = '\0'; if (ch == EOF && ptr == s) return (NULL); else return (s); } /* * 'smart_strlcpy()' - Copy a string and convert from ISO-8859-1 to UTF-8 as needed. */ static void smart_strlcpy(char *dst, /* I - Output buffer */ const char *src, /* I - Input string */ size_t dstsize) /* I - Size of output buffer */ { const unsigned char *srcptr; /* Pointer into input string */ unsigned char *dstptr, /* Pointer into output buffer */ *dstend; /* End of output buffer */ int saw_8859 = 0; /* Saw an extended character that was not UTF-8? */ for (srcptr = (unsigned char *)src, dstptr = (unsigned char *)dst, dstend = dstptr + dstsize - 1; *srcptr;) { if (*srcptr < 0x80) *dstptr++ = *srcptr++; /* ASCII */ else if (saw_8859) { /* * Map ISO-8859-1 (most likely character set for legacy LPD clients) to * UTF-8... */ if (dstptr > (dstend - 2)) break; *dstptr++ = 0xc0 | (*srcptr >> 6); *dstptr++ = 0x80 | (*srcptr++ & 0x3f); } else if ((*srcptr & 0xe0) == 0xc0 && (srcptr[1] & 0xc0) == 0x80) { /* * 2-byte UTF-8 sequence... */ if (dstptr > (dstend - 2)) break; *dstptr++ = *srcptr++; *dstptr++ = *srcptr++; } else if ((*srcptr & 0xf0) == 0xe0 && (srcptr[1] & 0xc0) == 0x80 && (srcptr[2] & 0xc0) == 0x80) { /* * 3-byte UTF-8 sequence... */ if (dstptr > (dstend - 3)) break; *dstptr++ = *srcptr++; *dstptr++ = *srcptr++; *dstptr++ = *srcptr++; } else if ((*srcptr & 0xf8) == 0xf0 && (srcptr[1] & 0xc0) == 0x80 && (srcptr[2] & 0xc0) == 0x80 && (srcptr[3] & 0xc0) == 0x80) { /* * 4-byte UTF-8 sequence... */ if (dstptr > (dstend - 4)) break; *dstptr++ = *srcptr++; *dstptr++ = *srcptr++; *dstptr++ = *srcptr++; *dstptr++ = *srcptr++; } else { /* * Bad UTF-8 sequence, this must be an ISO-8859-1 string... */ saw_8859 = 1; if (dstptr > (dstend - 2)) break; *dstptr++ = 0xc0 | (*srcptr >> 6); *dstptr++ = 0x80 | (*srcptr++ & 0x3f); } } *dstptr = '\0'; }