| /* |
| * Line Printer Daemon backend for CUPS. |
| * |
| * Copyright © 2007-2019 by Apple Inc. |
| * Copyright © 1997-2007 by Easy Software Products, all rights reserved. |
| * |
| * Licensed under Apache License v2.0. See the file "LICENSE" for more |
| * information. |
| */ |
| |
| /* |
| * Include necessary headers. |
| */ |
| |
| #include <cups/http-private.h> |
| #include "backend-private.h" |
| #include <stdarg.h> |
| #include <sys/types.h> |
| #include <sys/stat.h> |
| #include <stdio.h> |
| |
| #ifdef _WIN32 |
| # include <winsock.h> |
| #else |
| # include <sys/socket.h> |
| # include <netinet/in.h> |
| # include <arpa/inet.h> |
| # include <netdb.h> |
| #endif /* _WIN32 */ |
| #ifdef __APPLE__ |
| # include <CoreFoundation/CFNumber.h> |
| # include <CoreFoundation/CFPreferences.h> |
| #endif /* __APPLE__ */ |
| |
| |
| /* |
| * Globals... |
| */ |
| |
| static char tmpfilename[1024] = ""; /* Temporary spool file name */ |
| static int abort_job = 0; /* Non-zero if we get SIGTERM */ |
| |
| |
| /* |
| * Print mode... |
| */ |
| |
| #define MODE_STANDARD 0 /* Queue a copy */ |
| #define MODE_STREAM 1 /* Stream a copy */ |
| |
| |
| /* |
| * The order for control and data files in LPD requests... |
| */ |
| |
| #define ORDER_CONTROL_DATA 0 /* Control file first, then data */ |
| #define ORDER_DATA_CONTROL 1 /* Data file first, then control */ |
| |
| |
| /* |
| * What to reserve... |
| */ |
| |
| #define RESERVE_NONE 0 /* Don't reserve a priviledged port */ |
| #define RESERVE_RFC1179 1 /* Reserve port 721-731 */ |
| #define RESERVE_ANY 2 /* Reserve port 1-1023 */ |
| |
| |
| /* |
| * Local functions... |
| */ |
| |
| static int cups_rresvport(int *port, int family); |
| static int lpd_command(int lpd_fd, char *format, ...) |
| # ifdef __GNUC__ |
| __attribute__ ((__format__ (__printf__, 2, 3))) |
| # endif /* __GNUC__ */ |
| ; |
| static int lpd_queue(const char *hostname, http_addrlist_t *addrlist, const char *printer, int print_fd, int snmp_fd, int mode, const char *user, const char *title, int copies, int banner, int format, int order, int reserve, int manual_copies, int timeout, int contimeout, const char *orighost) _CUPS_NONNULL((1,2,3,7,8,17)); |
| static ssize_t lpd_write(int lpd_fd, char *buffer, size_t length); |
| static void sigterm_handler(int sig); |
| |
| |
| /* |
| * 'main()' - Send a file to the printer or server. |
| * |
| * Usage: |
| * |
| * printer-uri job-id user title copies options [file] |
| */ |
| |
| int /* O - Exit status */ |
| main(int argc, /* I - Number of command-line arguments (6 or 7) */ |
| char *argv[]) /* I - Command-line arguments */ |
| { |
| const char *device_uri; /* Device URI */ |
| char scheme[255], /* Scheme in URI */ |
| hostname[1024], /* Hostname */ |
| username[255], /* Username info */ |
| resource[1024], /* Resource info (printer name) */ |
| *options, /* Pointer to options */ |
| *name, /* Name of option */ |
| *value, /* Value of option */ |
| sep, /* Separator character */ |
| *filename, /* File to print */ |
| title[256]; /* Title string */ |
| int port; /* Port number */ |
| http_addrlist_t *addrlist; /* List of addresses for printer */ |
| int snmp_enabled = 1; /* Is SNMP enabled? */ |
| int snmp_fd; /* SNMP socket */ |
| int fd; /* Print file */ |
| int status; /* Status of LPD job */ |
| int mode; /* Print mode */ |
| int banner; /* Print banner page? */ |
| int format; /* Print format */ |
| int order; /* Order of control/data files */ |
| int reserve; /* Reserve priviledged port? */ |
| int sanitize_title; /* Sanitize title string? */ |
| int manual_copies, /* Do manual copies? */ |
| timeout, /* Timeout */ |
| contimeout, /* Connection timeout */ |
| copies; /* Number of copies */ |
| ssize_t bytes = 0; /* Initial bytes read */ |
| char buffer[16384]; /* Initial print buffer */ |
| #if defined(HAVE_SIGACTION) && !defined(HAVE_SIGSET) |
| struct sigaction action; /* Actions for POSIX signals */ |
| #endif /* HAVE_SIGACTION && !HAVE_SIGSET */ |
| int num_jobopts; /* Number of job options */ |
| cups_option_t *jobopts = NULL; /* Job options */ |
| |
| |
| /* |
| * Make sure status messages are not buffered... |
| */ |
| |
| setbuf(stderr, NULL); |
| |
| /* |
| * Ignore SIGPIPE and catch SIGTERM signals... |
| */ |
| |
| #ifdef HAVE_SIGSET |
| sigset(SIGPIPE, SIG_IGN); |
| sigset(SIGTERM, sigterm_handler); |
| #elif defined(HAVE_SIGACTION) |
| memset(&action, 0, sizeof(action)); |
| action.sa_handler = SIG_IGN; |
| sigaction(SIGPIPE, &action, NULL); |
| |
| sigemptyset(&action.sa_mask); |
| sigaddset(&action.sa_mask, SIGTERM); |
| action.sa_handler = sigterm_handler; |
| sigaction(SIGTERM, &action, NULL); |
| #else |
| signal(SIGPIPE, SIG_IGN); |
| signal(SIGTERM, sigterm_handler); |
| #endif /* HAVE_SIGSET */ |
| |
| /* |
| * Check command-line... |
| */ |
| |
| if (argc == 1) |
| { |
| printf("network lpd \"Unknown\" \"%s\"\n", |
| _cupsLangString(cupsLangDefault(), _("LPD/LPR Host or Printer"))); |
| return (CUPS_BACKEND_OK); |
| } |
| else if (argc < 6 || argc > 7) |
| { |
| _cupsLangPrintf(stderr, |
| _("Usage: %s job-id user title copies options [file]"), |
| argv[0]); |
| return (CUPS_BACKEND_FAILED); |
| } |
| |
| num_jobopts = cupsParseOptions(argv[5], 0, &jobopts); |
| |
| /* |
| * Extract the hostname and printer name from the URI... |
| */ |
| |
| while ((device_uri = cupsBackendDeviceURI(argv)) == NULL) |
| { |
| _cupsLangPrintFilter(stderr, "INFO", _("Unable to locate printer.")); |
| sleep(10); |
| |
| if (getenv("CLASS") != NULL) |
| return (CUPS_BACKEND_FAILED); |
| } |
| |
| httpSeparateURI(HTTP_URI_CODING_ALL, device_uri, scheme, sizeof(scheme), |
| username, sizeof(username), hostname, sizeof(hostname), &port, |
| resource, sizeof(resource)); |
| |
| if (!port) |
| port = 515; /* Default to port 515 */ |
| |
| if (!username[0]) |
| { |
| /* |
| * If no username is in the device URI, then use the print job user... |
| */ |
| |
| strlcpy(username, argv[2], sizeof(username)); |
| } |
| |
| /* |
| * See if there are any options... |
| */ |
| |
| mode = MODE_STANDARD; |
| banner = 0; |
| format = 'l'; |
| order = ORDER_CONTROL_DATA; |
| reserve = RESERVE_ANY; |
| manual_copies = 1; |
| timeout = 300; |
| contimeout = 7 * 24 * 60 * 60; |
| |
| #ifdef __APPLE__ |
| /* |
| * We want to pass UTF-8 characters by default, not re-map them (3071945) |
| */ |
| |
| sanitize_title = 0; |
| #else |
| /* |
| * Otherwise we want to re-map UTF-8 to "safe" characters by default... |
| */ |
| |
| sanitize_title = 1; |
| #endif /* __APPLE__ */ |
| |
| if ((options = strchr(resource, '?')) != NULL) |
| { |
| /* |
| * Yup, terminate the device name string and move to the first |
| * character of the options... |
| */ |
| |
| *options++ = '\0'; |
| |
| /* |
| * Parse options... |
| */ |
| |
| while (*options) |
| { |
| /* |
| * Get the name... |
| */ |
| |
| name = options; |
| |
| while (*options && *options != '=' && *options != '+' && *options != '&') |
| options ++; |
| |
| if ((sep = *options) != '\0') |
| *options++ = '\0'; |
| |
| if (sep == '=') |
| { |
| /* |
| * Get the value... |
| */ |
| |
| value = options; |
| |
| while (*options && *options != '+' && *options != '&') |
| options ++; |
| |
| if (*options) |
| *options++ = '\0'; |
| } |
| else |
| value = (char *)""; |
| |
| /* |
| * Process the option... |
| */ |
| |
| if (!_cups_strcasecmp(name, "banner")) |
| { |
| /* |
| * Set the banner... |
| */ |
| |
| banner = !value[0] || !_cups_strcasecmp(value, "on") || |
| !_cups_strcasecmp(value, "yes") || !_cups_strcasecmp(value, "true"); |
| } |
| else if (!_cups_strcasecmp(name, "format") && value[0]) |
| { |
| /* |
| * Set output format... |
| */ |
| |
| if (strchr("cdfglnoprtv", value[0])) |
| format = value[0]; |
| else |
| _cupsLangPrintFilter(stderr, "ERROR", |
| _("Unknown format character: \"%c\"."), |
| value[0]); |
| } |
| else if (!_cups_strcasecmp(name, "mode") && value[0]) |
| { |
| /* |
| * Set control/data order... |
| */ |
| |
| if (!_cups_strcasecmp(value, "standard")) |
| mode = MODE_STANDARD; |
| else if (!_cups_strcasecmp(value, "stream")) |
| mode = MODE_STREAM; |
| else |
| _cupsLangPrintFilter(stderr, "ERROR", |
| _("Unknown print mode: \"%s\"."), value); |
| } |
| else if (!_cups_strcasecmp(name, "order") && value[0]) |
| { |
| /* |
| * Set control/data order... |
| */ |
| |
| if (!_cups_strcasecmp(value, "control,data")) |
| order = ORDER_CONTROL_DATA; |
| else if (!_cups_strcasecmp(value, "data,control")) |
| order = ORDER_DATA_CONTROL; |
| else |
| _cupsLangPrintFilter(stderr, "ERROR", |
| _("Unknown file order: \"%s\"."), value); |
| } |
| else if (!_cups_strcasecmp(name, "reserve")) |
| { |
| /* |
| * Set port reservation mode... |
| */ |
| |
| if (!value[0] || !_cups_strcasecmp(value, "on") || |
| !_cups_strcasecmp(value, "yes") || |
| !_cups_strcasecmp(value, "true") || |
| !_cups_strcasecmp(value, "rfc1179")) |
| reserve = RESERVE_RFC1179; |
| else if (!_cups_strcasecmp(value, "any")) |
| reserve = RESERVE_ANY; |
| else |
| reserve = RESERVE_NONE; |
| } |
| else if (!_cups_strcasecmp(name, "manual_copies")) |
| { |
| /* |
| * Set manual copies... |
| */ |
| |
| manual_copies = !value[0] || !_cups_strcasecmp(value, "on") || |
| !_cups_strcasecmp(value, "yes") || |
| !_cups_strcasecmp(value, "true"); |
| } |
| else if (!_cups_strcasecmp(name, "sanitize_title")) |
| { |
| /* |
| * Set sanitize title... |
| */ |
| |
| sanitize_title = !value[0] || !_cups_strcasecmp(value, "on") || |
| !_cups_strcasecmp(value, "yes") || |
| !_cups_strcasecmp(value, "true"); |
| } |
| else if (!_cups_strcasecmp(name, "snmp")) |
| { |
| /* |
| * Enable/disable SNMP stuff... |
| */ |
| |
| snmp_enabled = !value[0] || !_cups_strcasecmp(value, "on") || |
| !_cups_strcasecmp(value, "yes") || |
| !_cups_strcasecmp(value, "true"); |
| } |
| else if (!_cups_strcasecmp(name, "timeout")) |
| { |
| /* |
| * Set the timeout... |
| */ |
| |
| if (atoi(value) > 0) |
| timeout = atoi(value); |
| } |
| else if (!_cups_strcasecmp(name, "contimeout")) |
| { |
| /* |
| * Set the connection timeout... |
| */ |
| |
| if (atoi(value) > 0) |
| contimeout = atoi(value); |
| } |
| } |
| } |
| |
| if (mode == MODE_STREAM) |
| order = ORDER_CONTROL_DATA; |
| |
| /* |
| * Find the printer... |
| */ |
| |
| addrlist = backendLookup(hostname, port, NULL); |
| |
| /* |
| * See if the printer supports SNMP... |
| */ |
| |
| if (snmp_enabled) |
| snmp_fd = _cupsSNMPOpen(addrlist->addr.addr.sa_family); |
| else |
| snmp_fd = -1; |
| |
| /* |
| * Wait for data from the filter... |
| */ |
| |
| if (argc == 6) |
| { |
| if (!backendWaitLoop(snmp_fd, &(addrlist->addr), 0, backendNetworkSideCB)) |
| return (CUPS_BACKEND_OK); |
| else if (mode == MODE_STANDARD && |
| (bytes = read(0, buffer, sizeof(buffer))) <= 0) |
| return (CUPS_BACKEND_OK); |
| } |
| |
| /* |
| * If we have 7 arguments, print the file named on the command-line. |
| * Otherwise, copy stdin to a temporary file and print the temporary |
| * file. |
| */ |
| |
| if (argc == 6 && mode == MODE_STANDARD) |
| { |
| /* |
| * Copy stdin to a temporary file... |
| */ |
| |
| if ((fd = cupsTempFd(tmpfilename, sizeof(tmpfilename))) < 0) |
| { |
| perror("DEBUG: Unable to create temporary file"); |
| return (CUPS_BACKEND_FAILED); |
| } |
| |
| _cupsLangPrintFilter(stderr, "INFO", _("Copying print data.")); |
| |
| if (bytes > 0) |
| write(fd, buffer, (size_t)bytes); |
| |
| backendRunLoop(-1, fd, snmp_fd, &(addrlist->addr), 0, 0, |
| backendNetworkSideCB); |
| } |
| else if (argc == 6) |
| { |
| /* |
| * Stream from stdin... |
| */ |
| |
| filename = NULL; |
| fd = 0; |
| } |
| else |
| { |
| filename = argv[6]; |
| fd = open(filename, O_RDONLY); |
| |
| if (fd == -1) |
| { |
| _cupsLangPrintError("ERROR", _("Unable to open print file")); |
| return (CUPS_BACKEND_FAILED); |
| } |
| } |
| |
| /* |
| * Sanitize the document title... |
| */ |
| |
| strlcpy(title, argv[3], sizeof(title)); |
| |
| if (sanitize_title) |
| { |
| /* |
| * Sanitize the title string so that we don't cause problems on |
| * the remote end... |
| */ |
| |
| char *ptr; |
| |
| for (ptr = title; *ptr; ptr ++) |
| if (!isalnum(*ptr & 255) && !isspace(*ptr & 255)) |
| *ptr = '_'; |
| } |
| |
| /* |
| * Queue the job... |
| */ |
| |
| if (argc > 6) |
| { |
| if (manual_copies) |
| { |
| manual_copies = atoi(argv[4]); |
| copies = 1; |
| } |
| else |
| { |
| manual_copies = 1; |
| copies = atoi(argv[4]); |
| } |
| |
| status = lpd_queue(hostname, addrlist, resource + 1, fd, snmp_fd, mode, |
| username, title, copies, banner, format, order, reserve, |
| manual_copies, timeout, contimeout, |
| cupsGetOption("job-originating-host-name", num_jobopts, |
| jobopts)); |
| |
| if (!status) |
| fprintf(stderr, "PAGE: 1 %d\n", atoi(argv[4])); |
| } |
| else |
| status = lpd_queue(hostname, addrlist, resource + 1, fd, snmp_fd, mode, |
| username, title, 1, banner, format, order, reserve, 1, |
| timeout, contimeout, |
| cupsGetOption("job-originating-host-name", num_jobopts, |
| jobopts)); |
| |
| /* |
| * Remove the temporary file if necessary... |
| */ |
| |
| if (tmpfilename[0]) |
| unlink(tmpfilename); |
| |
| if (fd) |
| close(fd); |
| |
| if (snmp_fd >= 0) |
| _cupsSNMPClose(snmp_fd); |
| |
| /* |
| * Return the queue status... |
| */ |
| |
| return (status); |
| } |
| |
| |
| /* |
| * 'cups_rresvport()' - A simple implementation of rresvport_af(). |
| */ |
| |
| static int /* O - Socket or -1 on error */ |
| cups_rresvport(int *port, /* IO - Port number to bind to */ |
| int family) /* I - Address family */ |
| { |
| http_addr_t addr; /* Socket address */ |
| int fd; /* Socket file descriptor */ |
| |
| |
| /* |
| * Try to create an IPv4 socket... |
| */ |
| |
| if ((fd = socket(family, SOCK_STREAM, 0)) < 0) |
| return (-1); |
| |
| /* |
| * Initialize the address buffer... |
| */ |
| |
| memset(&addr, 0, sizeof(addr)); |
| addr.addr.sa_family = (sa_family_t)family; |
| |
| /* |
| * Try to bind the socket to a reserved port... |
| */ |
| |
| while (*port > 511) |
| { |
| /* |
| * Set the port number... |
| */ |
| |
| _httpAddrSetPort(&addr, *port); |
| |
| /* |
| * Try binding the port to the socket; return if all is OK... |
| */ |
| |
| if (!bind(fd, (struct sockaddr *)&addr, (socklen_t)httpAddrLength(&addr))) |
| return (fd); |
| |
| /* |
| * Stop if we have any error other than "address already in use"... |
| */ |
| |
| if (errno != EADDRINUSE) |
| { |
| httpAddrClose(NULL, fd); |
| |
| return (-1); |
| } |
| |
| /* |
| * Try the next port... |
| */ |
| |
| (*port)--; |
| } |
| |
| /* |
| * Wasn't able to bind to a reserved port, so close the socket and return |
| * -1... |
| */ |
| |
| #ifdef _WIN32 |
| closesocket(fd); |
| #else |
| close(fd); |
| #endif /* _WIN32 */ |
| |
| return (-1); |
| } |
| |
| |
| /* |
| * 'lpd_command()' - Send an LPR command sequence and wait for a reply. |
| */ |
| |
| static int /* O - Status of command */ |
| lpd_command(int fd, /* I - Socket connection to LPD host */ |
| char *format, /* I - printf()-style format string */ |
| ...) /* I - Additional args as necessary */ |
| { |
| va_list ap; /* Argument pointer */ |
| char buf[1024]; /* Output buffer */ |
| ssize_t bytes; /* Number of bytes to output */ |
| char status; /* Status from command */ |
| |
| |
| /* |
| * Don't try to send commands if the job has been canceled... |
| */ |
| |
| if (abort_job) |
| return (-1); |
| |
| /* |
| * Format the string... |
| */ |
| |
| va_start(ap, format); |
| bytes = vsnprintf(buf, sizeof(buf), format, ap); |
| va_end(ap); |
| |
| fprintf(stderr, "DEBUG: lpd_command %2.2x %s", buf[0], buf + 1); |
| |
| /* |
| * Send the command... |
| */ |
| |
| fprintf(stderr, "DEBUG: Sending command string (" CUPS_LLFMT " bytes)...\n", CUPS_LLCAST bytes); |
| |
| if (lpd_write(fd, buf, (size_t)bytes) < bytes) |
| { |
| perror("DEBUG: Unable to send LPD command"); |
| return (-1); |
| } |
| |
| /* |
| * Read back the status from the command and return it... |
| */ |
| |
| fputs("DEBUG: Reading command status...\n", stderr); |
| |
| if (recv(fd, &status, 1, 0) < 1) |
| { |
| _cupsLangPrintFilter(stderr, "WARNING", _("The printer did not respond.")); |
| status = (char)errno; |
| } |
| |
| fprintf(stderr, "DEBUG: lpd_command returning %d\n", status); |
| |
| return (status); |
| } |
| |
| |
| /* |
| * 'lpd_queue()' - Queue a file using the Line Printer Daemon protocol. |
| */ |
| |
| static int /* O - Zero on success, non-zero on failure */ |
| lpd_queue(const char *hostname, /* I - Host to connect to */ |
| http_addrlist_t *addrlist, /* I - List of host addresses */ |
| const char *printer, /* I - Printer/queue name */ |
| int print_fd, /* I - File to print */ |
| int snmp_fd, /* I - SNMP socket */ |
| int mode, /* I - Print mode */ |
| const char *user, /* I - Requesting user */ |
| const char *title, /* I - Job title */ |
| int copies, /* I - Number of copies */ |
| int banner, /* I - Print LPD banner? */ |
| int format, /* I - Format specifier */ |
| int order, /* I - Order of data/control files */ |
| int reserve, /* I - Reserve ports? */ |
| int manual_copies,/* I - Do copies by hand... */ |
| int timeout, /* I - Timeout... */ |
| int contimeout, /* I - Connection timeout */ |
| const char *orighost) /* I - job-originating-host-name */ |
| { |
| char localhost[255]; /* Local host name */ |
| int error; /* Error number */ |
| struct stat filestats; /* File statistics */ |
| int lport; /* LPD connection local port */ |
| int fd; /* LPD socket */ |
| char control[10240], /* LPD control 'file' */ |
| *cptr; /* Pointer into control file string */ |
| char status; /* Status byte from command */ |
| int delay; /* Delay for retries... */ |
| char addrname[256]; /* Address name */ |
| http_addrlist_t *addr; /* Socket address */ |
| int have_supplies; /* Printer supports supply levels? */ |
| int copy; /* Copies written */ |
| time_t start_time; /* Time of first connect */ |
| ssize_t nbytes; /* Number of bytes written */ |
| off_t tbytes; /* Total bytes written */ |
| char buffer[32768]; /* Output buffer */ |
| #ifdef _WIN32 |
| DWORD tv; /* Timeout in milliseconds */ |
| #else |
| struct timeval tv; /* Timeout in secs and usecs */ |
| #endif /* _WIN32 */ |
| |
| |
| /* |
| * Remember when we started trying to connect to the printer... |
| */ |
| |
| start_time = time(NULL); |
| |
| /* |
| * Loop forever trying to print the file... |
| */ |
| |
| while (!abort_job) |
| { |
| /* |
| * First try to reserve a port for this connection... |
| */ |
| |
| fprintf(stderr, "DEBUG: Connecting to %s:%d for printer %s\n", hostname, |
| httpAddrPort(&(addrlist->addr)), printer); |
| _cupsLangPrintFilter(stderr, "INFO", _("Connecting to printer.")); |
| |
| for (lport = reserve == RESERVE_RFC1179 ? 732 : 1024, addr = addrlist, |
| delay = 5;; |
| addr = addr->next) |
| { |
| /* |
| * Stop if this job has been canceled... |
| */ |
| |
| if (abort_job) |
| return (CUPS_BACKEND_FAILED); |
| |
| /* |
| * Choose the next priviledged port... |
| */ |
| |
| if (!addr) |
| addr = addrlist; |
| |
| lport --; |
| |
| if (lport < 721 && reserve == RESERVE_RFC1179) |
| lport = 731; |
| else if (lport < 1) |
| lport = 1023; |
| |
| #ifdef HAVE_GETEUID |
| if (geteuid() || !reserve) |
| #else |
| if (getuid() || !reserve) |
| #endif /* HAVE_GETEUID */ |
| { |
| /* |
| * Just create a regular socket... |
| */ |
| |
| if ((fd = socket(addr->addr.addr.sa_family, SOCK_STREAM, 0)) < 0) |
| { |
| perror("DEBUG: Unable to create socket"); |
| sleep(1); |
| |
| continue; |
| } |
| |
| lport = 0; |
| } |
| else |
| { |
| /* |
| * We're running as root and want to comply with RFC 1179. Reserve a |
| * priviledged lport between 721 and 731... |
| */ |
| |
| if ((fd = cups_rresvport(&lport, addr->addr.addr.sa_family)) < 0) |
| { |
| perror("DEBUG: Unable to reserve port"); |
| sleep(1); |
| |
| continue; |
| } |
| } |
| |
| /* |
| * Connect to the printer or server... |
| */ |
| |
| if (abort_job) |
| { |
| close(fd); |
| |
| return (CUPS_BACKEND_FAILED); |
| } |
| |
| if (!connect(fd, &(addr->addr.addr), (socklen_t)httpAddrLength(&(addr->addr)))) |
| break; |
| |
| error = errno; |
| close(fd); |
| |
| if (addr->next) |
| continue; |
| |
| if (getenv("CLASS") != NULL) |
| { |
| /* |
| * If the CLASS environment variable is set, the job was submitted |
| * to a class and not to a specific queue. In this case, we want |
| * to abort immediately so that the job can be requeued on the next |
| * available printer in the class. |
| */ |
| |
| _cupsLangPrintFilter(stderr, "INFO", |
| _("Unable to contact printer, queuing on next " |
| "printer in class.")); |
| |
| /* |
| * Sleep 5 seconds to keep the job from requeuing too rapidly... |
| */ |
| |
| sleep(5); |
| |
| return (CUPS_BACKEND_FAILED); |
| } |
| |
| fprintf(stderr, "DEBUG: Connection error: %s\n", strerror(error)); |
| |
| if (errno == ECONNREFUSED || errno == EHOSTDOWN || errno == EHOSTUNREACH || errno == ETIMEDOUT || errno == ENOTCONN) |
| { |
| if (contimeout && (time(NULL) - start_time) > contimeout) |
| { |
| _cupsLangPrintFilter(stderr, "ERROR", |
| _("The printer is not responding.")); |
| return (CUPS_BACKEND_FAILED); |
| } |
| |
| switch (error) |
| { |
| case EHOSTDOWN : |
| _cupsLangPrintFilter(stderr, "WARNING", |
| _("The printer may not exist or " |
| "is unavailable at this time.")); |
| break; |
| |
| case EHOSTUNREACH : |
| default : |
| _cupsLangPrintFilter(stderr, "WARNING", |
| _("The printer is unreachable at " |
| "this time.")); |
| break; |
| |
| case ECONNREFUSED : |
| _cupsLangPrintFilter(stderr, "WARNING", |
| _("The printer is in use.")); |
| break; |
| } |
| |
| sleep((unsigned)delay); |
| |
| if (delay < 30) |
| delay += 5; |
| } |
| else if (error == EADDRINUSE) |
| { |
| /* |
| * Try on another port... |
| */ |
| |
| sleep(1); |
| } |
| else |
| { |
| _cupsLangPrintFilter(stderr, "ERROR", |
| _("The printer is not responding.")); |
| sleep(30); |
| } |
| } |
| |
| /* |
| * Set the timeout... |
| */ |
| |
| #ifdef _WIN32 |
| tv = (DWORD)(timeout * 1000); |
| |
| setsockopt(fd, SOL_SOCKET, SO_RCVTIMEO, (char *)&tv, sizeof(tv)); |
| setsockopt(fd, SOL_SOCKET, SO_SNDTIMEO, (char *)&tv, sizeof(tv)); |
| #else |
| tv.tv_sec = timeout; |
| tv.tv_usec = 0; |
| |
| setsockopt(fd, SOL_SOCKET, SO_RCVTIMEO, &tv, sizeof(tv)); |
| setsockopt(fd, SOL_SOCKET, SO_SNDTIMEO, &tv, sizeof(tv)); |
| #endif /* _WIN32 */ |
| |
| fputs("STATE: -connecting-to-device\n", stderr); |
| _cupsLangPrintFilter(stderr, "INFO", _("Connected to printer.")); |
| |
| fprintf(stderr, "DEBUG: Connected to %s:%d (local port %d)...\n", |
| httpAddrString(&(addr->addr), addrname, sizeof(addrname)), |
| httpAddrPort(&(addr->addr)), lport); |
| |
| /* |
| * See if the printer supports SNMP... |
| */ |
| |
| if (snmp_fd >= 0) |
| have_supplies = !backendSNMPSupplies(snmp_fd, &(addrlist->addr), NULL, |
| NULL); |
| else |
| have_supplies = 0; |
| |
| /* |
| * Check for side-channel requests... |
| */ |
| |
| backendCheckSideChannel(snmp_fd, &(addrlist->addr)); |
| |
| /* |
| * Next, open the print file and figure out its size... |
| */ |
| |
| if (print_fd) |
| { |
| /* |
| * Use the size from the print file... |
| */ |
| |
| if (fstat(print_fd, &filestats)) |
| { |
| close(fd); |
| |
| perror("DEBUG: unable to stat print file"); |
| return (CUPS_BACKEND_FAILED); |
| } |
| |
| filestats.st_size *= manual_copies; |
| } |
| else |
| { |
| /* |
| * Use a "very large value" for the size so that the printer will |
| * keep printing until we close the connection... |
| */ |
| |
| #ifdef _LARGEFILE_SOURCE |
| filestats.st_size = (size_t)(999999999999.0); |
| #else |
| filestats.st_size = 2147483647; |
| #endif /* _LARGEFILE_SOURCE */ |
| } |
| |
| /* |
| * Send a job header to the printer, specifying no banner page and |
| * literal output... |
| */ |
| |
| if (lpd_command(fd, "\002%s\n", |
| printer)) /* Receive print job(s) */ |
| { |
| close(fd); |
| return (CUPS_BACKEND_FAILED); |
| } |
| |
| if (orighost && _cups_strcasecmp(orighost, "localhost")) |
| strlcpy(localhost, orighost, sizeof(localhost)); |
| else |
| httpGetHostname(NULL, localhost, sizeof(localhost)); |
| |
| snprintf(control, sizeof(control), |
| "H%.31s\n" /* RFC 1179, Section 7.2 - host name <= 31 chars */ |
| "P%.31s\n" /* RFC 1179, Section 7.2 - user name <= 31 chars */ |
| "J%.99s\n", /* RFC 1179, Section 7.2 - job name <= 99 chars */ |
| localhost, user, title); |
| cptr = control + strlen(control); |
| |
| if (banner) |
| { |
| snprintf(cptr, sizeof(control) - (size_t)(cptr - control), |
| "C%.31s\n" /* RFC 1179, Section 7.2 - class name <= 31 chars */ |
| "L%s\n", |
| localhost, user); |
| cptr += strlen(cptr); |
| } |
| |
| while (copies > 0) |
| { |
| snprintf(cptr, sizeof(control) - (size_t)(cptr - control), "%cdfA%03d%.15s\n", |
| format, (int)getpid() % 1000, localhost); |
| cptr += strlen(cptr); |
| copies --; |
| } |
| |
| snprintf(cptr, sizeof(control) - (size_t)(cptr - control), |
| "UdfA%03d%.15s\n" |
| "N%.131s\n", /* RFC 1179, Section 7.2 - sourcefile name <= 131 chars */ |
| (int)getpid() % 1000, localhost, title); |
| |
| fprintf(stderr, "DEBUG: Control file is:\n%s", control); |
| |
| if (order == ORDER_CONTROL_DATA) |
| { |
| /* |
| * Check for side-channel requests... |
| */ |
| |
| backendCheckSideChannel(snmp_fd, &(addr->addr)); |
| |
| /* |
| * Send the control file... |
| */ |
| |
| if (lpd_command(fd, "\002%d cfA%03d%.15s\n", (int)strlen(control), |
| (int)getpid() % 1000, localhost)) |
| { |
| close(fd); |
| |
| return (CUPS_BACKEND_FAILED); |
| } |
| |
| fprintf(stderr, "DEBUG: Sending control file (%u bytes)\n", |
| (unsigned)strlen(control)); |
| |
| if ((size_t)lpd_write(fd, control, strlen(control) + 1) < (strlen(control) + 1)) |
| { |
| status = (char)errno; |
| perror("DEBUG: Unable to write control file"); |
| |
| } |
| else |
| { |
| if (read(fd, &status, 1) < 1) |
| { |
| _cupsLangPrintFilter(stderr, "WARNING", |
| _("The printer did not respond.")); |
| status = (char)errno; |
| } |
| } |
| |
| if (status != 0) |
| _cupsLangPrintFilter(stderr, "ERROR", |
| _("Remote host did not accept control file (%d)."), |
| status); |
| else |
| _cupsLangPrintFilter(stderr, "INFO", |
| _("Control file sent successfully.")); |
| } |
| else |
| status = 0; |
| |
| if (status == 0) |
| { |
| /* |
| * Check for side-channel requests... |
| */ |
| |
| backendCheckSideChannel(snmp_fd, &(addr->addr)); |
| |
| /* |
| * Send the print file... |
| */ |
| |
| if (lpd_command(fd, "\003" CUPS_LLFMT " dfA%03d%.15s\n", |
| CUPS_LLCAST filestats.st_size, (int)getpid() % 1000, |
| localhost)) |
| { |
| close(fd); |
| |
| return (CUPS_BACKEND_FAILED); |
| } |
| |
| fprintf(stderr, "DEBUG: Sending data file (" CUPS_LLFMT " bytes)\n", |
| CUPS_LLCAST filestats.st_size); |
| |
| tbytes = 0; |
| for (copy = 0; copy < manual_copies; copy ++) |
| { |
| lseek(print_fd, 0, SEEK_SET); |
| |
| while ((nbytes = read(print_fd, buffer, sizeof(buffer))) > 0) |
| { |
| _cupsLangPrintFilter(stderr, "INFO", |
| _("Spooling job, %.0f%% complete."), |
| 100.0 * tbytes / filestats.st_size); |
| |
| if (lpd_write(fd, buffer, (size_t)nbytes) < nbytes) |
| { |
| perror("DEBUG: Unable to send print file to printer"); |
| break; |
| } |
| else |
| tbytes += nbytes; |
| } |
| } |
| |
| if (mode == MODE_STANDARD) |
| { |
| if (tbytes < filestats.st_size) |
| status = (char)errno; |
| else if (lpd_write(fd, "", 1) < 1) |
| { |
| perror("DEBUG: Unable to send trailing nul to printer"); |
| status = (char)errno; |
| } |
| else |
| { |
| /* |
| * Read the status byte from the printer; if we can't read the byte |
| * back now, we should set status to "errno", however at this point |
| * we know the printer got the whole file and we don't necessarily |
| * want to requeue it over and over... |
| */ |
| |
| if (recv(fd, &status, 1, 0) < 1) |
| { |
| _cupsLangPrintFilter(stderr, "WARNING", |
| _("The printer did not respond.")); |
| status = 0; |
| } |
| } |
| } |
| else |
| status = 0; |
| |
| if (status != 0) |
| _cupsLangPrintFilter(stderr, "ERROR", |
| _("Remote host did not accept data file (%d)."), |
| status); |
| else |
| _cupsLangPrintFilter(stderr, "INFO", |
| _("Data file sent successfully.")); |
| } |
| |
| if (status == 0 && order == ORDER_DATA_CONTROL) |
| { |
| /* |
| * Check for side-channel requests... |
| */ |
| |
| backendCheckSideChannel(snmp_fd, &(addr->addr)); |
| |
| /* |
| * Send control file... |
| */ |
| |
| if (lpd_command(fd, "\002%d cfA%03d%.15s\n", (int)strlen(control), |
| (int)getpid() % 1000, localhost)) |
| { |
| close(fd); |
| |
| return (CUPS_BACKEND_FAILED); |
| } |
| |
| fprintf(stderr, "DEBUG: Sending control file (%lu bytes)\n", |
| (unsigned long)strlen(control)); |
| |
| if ((size_t)lpd_write(fd, control, strlen(control) + 1) < (strlen(control) + 1)) |
| { |
| status = (char)errno; |
| perror("DEBUG: Unable to write control file"); |
| } |
| else |
| { |
| if (read(fd, &status, 1) < 1) |
| { |
| _cupsLangPrintFilter(stderr, "WARNING", |
| _("The printer did not respond.")); |
| status = (char)errno; |
| } |
| } |
| |
| if (status != 0) |
| _cupsLangPrintFilter(stderr, "ERROR", |
| _("Remote host did not accept control file (%d)."), |
| status); |
| else |
| _cupsLangPrintFilter(stderr, "INFO", |
| _("Control file sent successfully.")); |
| } |
| |
| fputs("STATE: +cups-waiting-for-job-completed\n", stderr); |
| |
| /* |
| * Collect the final supply levels as needed... |
| */ |
| |
| if (have_supplies) |
| backendSNMPSupplies(snmp_fd, &(addr->addr), NULL, NULL); |
| |
| /* |
| * Close the socket connection and input file... |
| */ |
| |
| close(fd); |
| |
| if (status == 0) |
| return (CUPS_BACKEND_OK); |
| |
| /* |
| * Waiting for a retry... |
| */ |
| |
| sleep(30); |
| } |
| |
| /* |
| * If we get here, then the job has been canceled... |
| */ |
| |
| return (CUPS_BACKEND_FAILED); |
| } |
| |
| |
| /* |
| * 'lpd_write()' - Write a buffer of data to an LPD server. |
| */ |
| |
| static ssize_t /* O - Number of bytes written or -1 on error */ |
| lpd_write(int lpd_fd, /* I - LPD socket */ |
| char *buffer, /* I - Buffer to write */ |
| size_t length) /* I - Number of bytes to write */ |
| { |
| ssize_t bytes, /* Number of bytes written */ |
| total; /* Total number of bytes written */ |
| |
| |
| if (abort_job) |
| return (-1); |
| |
| total = 0; |
| while ((bytes = send(lpd_fd, buffer, length - (size_t)total, 0)) >= 0) |
| { |
| total += bytes; |
| buffer += bytes; |
| |
| if ((size_t)total == length) |
| break; |
| } |
| |
| if (bytes < 0) |
| return (-1); |
| else |
| return (total); |
| } |
| |
| |
| /* |
| * 'sigterm_handler()' - Handle 'terminate' signals that stop the backend. |
| */ |
| |
| static void |
| sigterm_handler(int sig) /* I - Signal */ |
| { |
| (void)sig; /* remove compiler warnings... */ |
| |
| abort_job = 1; |
| } |