/* $OpenBSD: printer.c,v 1.3 2021/10/24 21:24:18 deraadt Exp $ */ /* * Copyright (c) 2017 Eric Faurot * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "lpd.h" #include "lp.h" #include "log.h" #define RETRY_MAX 5 #define JOB_OK 0 #define JOB_AGAIN 1 #define JOB_IGNORE 2 #define JOB_ERROR 3 enum { OK = 0, ERR_TRANSIENT, /* transient error */ ERR_ACCOUNT, /* account required on the local machine */ ERR_ACCESS, /* cannot read file */ ERR_INODE, /* inode changed */ ERR_NOIMPL, /* unimplemented feature */ ERR_REJECTED, /* remote server rejected a job */ ERR_ERROR, /* filter report an error */ ERR_FILTER, /* filter return invalid status */ }; struct job { char *class; char *host; char *literal; char *mail; char *name; char *person; char *statinfo; char *title; int indent; int pagewidth; }; struct prnstate { int pfd; /* printer fd */ int ofilter; /* use output filter when printing */ int ofd; /* output filter fd */ pid_t opid; /* output filter process */ int tof; /* true if at top of form */ int count; /* number of printed files */ char efile[64]; /* filename for filter stderr */ }; static void sighandler(int); static char *xstrdup(const char *); static int openfile(const char *, const char *, struct stat *, FILE **); static int printjob(const char *, int); static void printbanner(struct job *); static int printfile(struct job *, int, const char *, const char *); static int sendjob(const char *, int); static int sendcmd(const char *, ...); static int sendfile(int, const char *, const char *); static int recvack(void); static void mailreport(struct job *, int); static void prn_open(void); static int prn_connect(void); static void prn_close(void); static int prn_fstart(void); static void prn_fsuspend(void); static void prn_fresume(void); static void prn_fclose(void); static int prn_formfeed(void); static int prn_write(const char *, size_t); static int prn_writefile(FILE *); static int prn_puts(const char *); static ssize_t prn_read(char *, size_t); static struct lp_printer *lp; static struct prnstate *prn; void printer(int debug, int verbose, const char *name) { struct sigaction sa; struct passwd *pw; struct lp_queue q; int fd, jobidx, qstate, r, reload, retry; char buf[64], curr[1024]; /* Early initialisation. */ log_init(debug, LOG_LPR); log_setverbose(verbose); snprintf(buf, sizeof(buf), "printer:%s", name); log_procinit(buf); setproctitle("%s", buf); if ((lpd_hostname = malloc(HOST_NAME_MAX+1)) == NULL) fatal("%s: malloc", __func__); gethostname(lpd_hostname, HOST_NAME_MAX+1); /* Detach from lpd session if not in debug mode. */ if (!debug) if (setsid() == -1) fatal("%s: setsid", __func__); /* Read printer config. */ if ((lp = calloc(1, sizeof(*lp))) == NULL) fatal("%s: calloc", __func__); if (lp_getprinter(lp, name) == -1) exit(1); /* * Redirect stderr if not in debug mode. * This must be done before dropping priviledges. */ if (!debug) { fd = open(LP_LF(lp), O_WRONLY|O_APPEND); if (fd == -1) fatal("%s: open: %s", __func__, LP_LF(lp)); if (fd != STDERR_FILENO) { if (dup2(fd, STDERR_FILENO) == -1) fatalx("%s: dup2", __func__); (void)close(fd); } } /* Drop priviledges. */ if ((pw = getpwnam(LPD_USER)) == NULL) fatalx("unknown user " LPD_USER); if (setgroups(1, &pw->pw_gid) || setresgid(pw->pw_gid, pw->pw_gid, pw->pw_gid) || setresuid(pw->pw_uid, pw->pw_uid, pw->pw_uid)) fatal("cannot drop privileges"); /* Initialize the printer state. */ if ((prn = calloc(1, sizeof(*prn))) == NULL) fatal("%s: calloc", __func__); prn->pfd = -1; prn->ofd = -1; /* Setup signals */ memset(&sa, 0, sizeof(sa)); sa.sa_handler = sighandler; sa.sa_flags = SA_RESTART; sigemptyset(&sa.sa_mask); sigaddset(&sa.sa_mask, SIGINT); /* for kill() in sighandler */ sigaction(SIGHUP, &sa, NULL); sigaction(SIGINT, &sa, NULL); sigaction(SIGQUIT, &sa, NULL); sigaction(SIGTERM, &sa, NULL); /* Grab lock file. */ if (lp_lock(lp) == -1) { if (errno == EWOULDBLOCK) { log_debug("already locked"); exit(0); } fatalx("cannot open lock file"); } /* Pledge. */ switch (lp->lp_type) { case PRN_LOCAL: pledge("stdio rpath wpath cpath flock getpw tty proc exec", NULL); break; case PRN_NET: pledge("stdio rpath wpath cpath inet flock dns getpw proc exec", NULL); break; case PRN_LPR: pledge("stdio rpath wpath cpath inet flock dns getpw", NULL); break; } /* Start processing the queue. */ memset(&q, 0, sizeof(q)); jobidx = 0; reload = 1; retry = 0; curr[0] = '\0'; for (;;) { /* Check the queue state. */ if (lp_getqueuestate(lp, 1, &qstate) == -1) fatalx("cannot get queue state"); if (qstate & LPQ_PRINTER_DOWN) { log_debug("printing disabled"); break; } if (qstate & LPQ_QUEUE_UPDATED) { log_debug("queue updated"); if (reload == 0) lp_clearqueue(&q); reload = 1; } /* Read the queue if needed. */ if (reload || q.count == 0) { if (lp_readqueue(lp, &q) == -1) fatalx("cannot read queue"); jobidx = 0; reload = 0; } /* If the queue is empty, all done */ if (q.count <= jobidx) { log_debug("queue empty"); break; } /* Open the printer if needed. */ if (prn->pfd == -1) { prn_open(); /* * Opening the printer might take some time. * Re-read the queue in case its state has changed. */ lp_clearqueue(&q); reload = 1; continue; } if (strcmp(curr, q.cfname[jobidx])) retry = 0; else strlcpy(curr, q.cfname[jobidx], sizeof(curr)); lp_setcurrtask(lp, q.cfname[jobidx]); if (lp->lp_type == PRN_LPR) r = sendjob(q.cfname[jobidx], retry); else r = printjob(q.cfname[jobidx], retry); lp_setcurrtask(lp, NULL); switch (r) { case JOB_OK: log_info("job %s %s successfully", q.cfname[jobidx], (lp->lp_type == PRN_LPR) ? "relayed" : "printed"); break; case JOB_AGAIN: retry++; continue; case JOB_IGNORE: break; case JOB_ERROR: log_warnx("job %s could not be printed", q.cfname[jobidx]); break; } curr[0] = '\0'; jobidx++; retry = 0; } if (prn->pfd != -1) { if (prn->count) { prn_formfeed(); if (lp->lp_tr) prn_puts(lp->lp_tr); } prn_close(); } exit(0); } static void sighandler(int code) { log_info("got signal %d", code); exit(0); } static char * xstrdup(const char *s) { char *r; if ((r = strdup(s)) == NULL) fatal("strdup"); return r; } /* * Open control/data file, and check that the inode information is valid. * On success, fill the "st" structure and set "fpp" and return 0 (OK). * Return an error code on error. */ static int openfile(const char *fname, const char *inodeinfo, struct stat *st, FILE **fpp) { FILE *fp; char buf[64]; if (inodeinfo) { log_warnx("cannot open %s: symlink not implemented", fname); return ERR_NOIMPL; } else { if ((fp = lp_fopen(lp, fname)) == NULL) { log_warn("cannot open %s", fname); return ERR_ACCESS; } } if (fstat(fileno(fp), st) == -1) { log_warn("%s: fstat: %s", __func__, fname); fclose(fp); return ERR_ACCESS; } if (inodeinfo) { snprintf(buf, sizeof(buf), "%d %llu", st->st_dev, st->st_ino); if (strcmp(inodeinfo, buf)) { log_warnx("inode changed for %s", fname); fclose(fp); return ERR_INODE; } } *fpp = fp; return OK; } /* * Print the job described by the control file. */ static int printjob(const char *cfname, int retry) { struct job job; FILE *fp; ssize_t len; size_t linesz = 0; char *line = NULL; const char *errstr; long long num; int r, ret = JOB_OK; log_debug("printing job %s...", cfname); prn->efile[0] = '\0'; memset(&job, 0, sizeof(job)); job.pagewidth = lp->lp_pw; if ((fp = lp_fopen(lp, cfname)) == NULL) { if (errno == ENOENT) { log_info("missing control file %s", cfname); return JOB_IGNORE; } /* XXX no fatal? */ fatal("cannot open %s", cfname); } /* First pass: setup the job structure, print banner and print data. */ while ((len = getline(&line, &linesz, fp)) != -1) { if (line[len-1] == '\n') line[len-1] = '\0'; switch (line[0]) { case 'C': /* Classification */ if (line[1]) { free(job.class); job.class = xstrdup(line + 1); } else if (job.class == NULL) job.class = xstrdup(lpd_hostname); break; case 'H': /* Host name */ free(job.host); job.host = xstrdup(line + 1); if (job.class == NULL) job.class = xstrdup(line + 1); break; case 'I': /* Indent */ errstr = NULL; num = strtonum(line + 1, 0, INT_MAX, &errstr); if (errstr == NULL) job.indent = num; else log_warnx("strtonum: %s", errstr); break; case 'J': /* Job Name */ free(job.name); if (line[1]) job.name = strdup(line + 1); else job.name = strdup(" "); break; case 'L': /* Literal */ free(job.literal); job.literal = xstrdup(line + 1); if (!lp->lp_sh && !lp->lp_hl) printbanner(&job); break; case 'M': /* Send mail to the specified user */ free(job.mail); job.mail = xstrdup(line + 1); break; case 'N': /* Filename */ break; case 'P': /* Person */ free(job.person); job.person = xstrdup(line + 1); if (lp->lp_rs && getpwnam(job.person) == NULL) { mailreport(&job, ERR_ACCOUNT); ret = JOB_ERROR; goto remove; } break; case 'S': /* Stat info for symlink protection */ job.statinfo = xstrdup(line + 1); break; case 'T': /* Title for pr */ job.title = xstrdup(line + 1); break; case 'U': /* Unlink */ break; case 'W': /* Width */ errstr = NULL; num = strtonum(line + 1, 0, INT_MAX, &errstr); if (errstr == NULL) job.pagewidth = num; else log_warnx("strtonum: %s", errstr); break; case '1': /* troff fonts */ case '2': case '3': case '4': /* XXX not implemented */ break; default: if (line[0] < 'a' || line[0] > 'z') break; r = printfile(&job, line[0], line+1, job.statinfo); free(job.statinfo); job.statinfo = NULL; free(job.title); job.title = NULL; if (r) { if (r == ERR_TRANSIENT && retry < RETRY_MAX) { ret = JOB_AGAIN; goto done; } mailreport(&job, r); ret = JOB_ERROR; goto remove; } } } remove: if (lp_unlink(lp, cfname) == -1) log_warn("cannot unlink %s", cfname); /* Second pass: print trailing banner, mail report, and remove files. */ rewind(fp); while ((len = getline(&line, &linesz, fp)) != -1) { if (line[len-1] == '\n') line[len-1] = '\0'; switch (line[0]) { case 'L': /* Literal */ if (ret != JOB_OK) break; if (!lp->lp_sh && lp->lp_hl) printbanner(&job); break; case 'M': /* Send mail to the specified user */ if (ret == JOB_OK) mailreport(&job, ret); break; case 'U': /* Unlink */ if (lp_unlink(lp, line + 1) == -1) log_warn("cannot unlink %s", line + 1); break; } } done: if (prn->efile[0]) unlink(prn->efile); (void)fclose(fp); free(job.class); free(job.host); free(job.literal); free(job.mail); free(job.name); free(job.person); free(job.statinfo); free(job.title); return ret; } static void printbanner(struct job *job) { time_t t; time(&t); prn_formfeed(); if (lp->lp_sb) { if (job->class) { prn_puts(job->class); prn_puts(":"); } prn_puts(job->literal); prn_puts(" Job: "); prn_puts(job->name); prn_puts(" Date: "); prn_puts(ctime(&t)); prn_puts("\n"); } else { prn_puts("\n\n\n"); lp_banner(prn->pfd, job->literal, lp->lp_pw); prn_puts("\n\n"); lp_banner(prn->pfd, job->name, lp->lp_pw); if (job->class) { prn_puts("\n\n\n"); lp_banner(prn->pfd, job->class, lp->lp_pw); } prn_puts("\n\n\n\n\t\t\t\t\tJob: "); prn_puts(job->name); prn_puts("\n\t\t\t\t\tDate: "); prn_puts(ctime(&t)); prn_puts("\n"); } prn_formfeed(); } static int printfile(struct job *job, int fmt, const char *fname, const char *inodeinfo) { pid_t pid; struct stat st; FILE *fp; size_t n; int ret, argc, efd, status; char *argv[16], *prog, width[16], length[16], indent[16], tmp[512]; log_debug("printing file %s...", fname); switch (fmt) { case 'f': /* print file as-is */ case 'o': /* print postscript file */ case 'l': /* print file as-is but pass control chars */ break; case 'p': /* print using pr(1) */ case 'r': /* print fortran text file */ case 't': /* print troff output */ case 'n': /* print ditroff output */ case 'd': /* print tex output */ case 'c': /* print cifplot output */ case 'g': /* print plot output */ case 'v': /* print raster output */ default: log_warn("unrecognized output format '%c'", fmt); return ERR_NOIMPL; } if ((ret = openfile(fname, inodeinfo, &st, &fp)) != OK) return ret; prn_formfeed(); /* * No input filter, just write the raw file. */ if (!lp->lp_if) { if (prn_writefile(fp) == -1) ret = ERR_TRANSIENT; else ret = OK; (void)fclose(fp); return ret; } /* * Otherwise, run the input filter with proper plumbing. */ /* Prepare filter arguments. */ snprintf(width, sizeof(width), "-w%d", job->pagewidth); snprintf(length, sizeof(length), "-l%ld", lp->lp_pl); snprintf(indent, sizeof(indent), "-i%d", job->indent); prog = strrchr(lp->lp_if, '/'); argc = 0; argv[argc++] = prog ? (prog + 1) : lp->lp_if; if (fmt == 'l') argv[argc++] = "-c"; argv[argc++] = width; argv[argc++] = length; argv[argc++] = indent; argv[argc++] = "-n"; argv[argc++] = job->person; if (job->name) { argv[argc++] = "-j"; argv[argc++]= job->name; } argv[argc++] = "-h"; argv[argc++] = job->host; argv[argc++] = lp->lp_af; argv[argc++] = NULL; /* Open the stderr file. */ strlcpy(prn->efile, "/tmp/prn.XXXXXXXX", sizeof(prn->efile)); if ((efd = mkstemp(prn->efile)) == -1) { log_warn("%s: mkstemp", __func__); (void)fclose(fp); return ERR_TRANSIENT; } /* Disable output filter. */ prn_fsuspend(); /* Run input filter */ switch ((pid = fork())) { case -1: log_warn("%s: fork", __func__); close(efd); prn_fresume(); return ERR_TRANSIENT; case 0: if (dup2(fileno(fp), STDIN_FILENO) == -1) fatal("%s:, dup2", __func__); if (dup2(prn->pfd, STDOUT_FILENO) == -1) fatal("%s:, dup2", __func__); if (dup2(efd, STDERR_FILENO) == -1) fatal("%s:, dup2", __func__); if (closefrom(3) == -1) fatal("%s:, closefrom", __func__); execv(lp->lp_if, argv); log_warn("%s:, execv", __func__); exit(2); default: break; } log_debug("waiting for ifilter..."); /* Wait for input filter to finish. */ while (waitpid(pid, &status, 0) == -1) log_warn("%s: waitpid", __func__); log_debug("ifilter done, status %d", status); /* Resume output filter */ prn_fresume(); prn->tof = 0; /* Copy efd to stderr */ if (lseek(efd, 0, SEEK_SET) == -1) log_warn("%s: lseek", __func__); while ((n = read(efd, tmp, sizeof(tmp))) > 0) (void)write(STDERR_FILENO, tmp, n); close(efd); if (!WIFEXITED(status)) { log_warn("filter terminated (termsig=%d)", WTERMSIG(status)); return ERR_FILTER; } switch (WEXITSTATUS(status)) { case 0: prn->tof = 1; return OK; case 1: return ERR_TRANSIENT; case 2: return ERR_ERROR; default: log_warn("filter exited (exitstatus=%d)", WEXITSTATUS(status)); return ERR_FILTER; } } static int sendjob(const char *cfname, int retry) { struct job job; FILE *fp; ssize_t len; size_t linesz = 0; char *line = NULL; int ret = JOB_OK, r; log_debug("sending job %s...", cfname); memset(&job, 0, sizeof(job)); if ((fp = lp_fopen(lp, cfname)) == NULL) { if (errno == ENOENT) { log_info("missing control file %s", cfname); return JOB_IGNORE; } /* XXX no fatal? */ fatal("cannot open %s", cfname); } /* First pass: setup the job structure, and forward data files. */ while ((len = getline(&line, &linesz, fp)) != -1) { if (line[len-1] == '\n') line[len-1] = '\0'; switch (line[0]) { case 'P': free(job.person); job.person = xstrdup(line + 1); break; case 'S': free(job.statinfo); job.statinfo = xstrdup(line + 1); break; default: if (line[0] < 'a' || line[0] > 'z') break; r = sendfile('\3', line+1, job.statinfo); free(job.statinfo); job.statinfo = NULL; if (r) { if (r == ERR_TRANSIENT && retry < RETRY_MAX) { ret = JOB_AGAIN; goto done; } mailreport(&job, r); ret = JOB_ERROR; goto remove; } } } /* Send the control file. */ if ((r = sendfile('\2', cfname, ""))) { if (r == ERR_TRANSIENT && retry < RETRY_MAX) { ret = JOB_AGAIN; goto done; } mailreport(&job, r); ret = JOB_ERROR; } remove: if (lp_unlink(lp, cfname) == -1) log_warn("cannot unlink %s", cfname); /* Second pass: remove files. */ rewind(fp); while ((len = getline(&line, &linesz, fp)) != -1) { if (line[len-1] == '\n') line[len-1] = '\0'; switch (line[0]) { case 'U': if (lp_unlink(lp, line + 1) == -1) log_warn("cannot unlink %s", line + 1); break; } } done: (void)fclose(fp); free(line); free(job.person); free(job.statinfo); return ret; } /* * Send a LPR command to the remote lpd server and return the ack. * Return 0 for ack, 1 or nack, -1 and set errno on error. */ static int sendcmd(const char *fmt, ...) { va_list ap; unsigned char line[1024]; int len; va_start(ap, fmt); len = vsnprintf(line, sizeof(line), fmt, ap); va_end(ap); if (len < 0) { log_warn("%s: vsnprintf", __func__); return -1; } if (prn_puts(line) == -1) return -1; return recvack(); } static int sendfile(int type, const char *fname, const char *inodeinfo) { struct stat st; FILE *fp = NULL; int ret; log_debug("sending file %s...", fname); if ((ret = openfile(fname, inodeinfo, &st, &fp)) != OK) return ret; ret = ERR_TRANSIENT; if (sendcmd("%c%lld %s\n", type, (long long)st.st_size, fname)) { if (errno == 0) ret = ERR_REJECTED; goto fail; } lp_setstatus(lp, "sending %s to %s", fname, lp->lp_rm); if (prn_writefile(fp) == -1 || prn_write("\0", 1) == -1) goto fail; if (recvack()) { if (errno == 0) ret = ERR_REJECTED; goto fail; } ret = OK; fail: (void)fclose(fp); if (ret == ERR_REJECTED) log_warnx("%s rejected by remote host", fname); return ret; } /* * Read a ack response from the server. * Return 0 for ack, 1 or nack, -1 and set errno on error. */ static int recvack(void) { char visbuf[256 * 4 + 1]; unsigned char line[1024]; ssize_t n; if ((n = prn_read(line, sizeof(line))) == -1) return -1; if (n == 1) { errno = 0; if (line[0]) log_warnx("%s: \\%d", lp->lp_host, line[0]); return line[0] ? 1 : 0; } if (n > 256) n = 256; line[n] = '\0'; if (line[n-1] == '\n') line[--n] = '\0'; strvisx(visbuf, line, n, VIS_NL | VIS_CSTYLE); log_warnx("%s: %s", lp->lp_host, visbuf); errno = 0; return -1; } static void mailreport(struct job *job, int result) { struct stat st; FILE *fp = NULL, *efp; const char *user; char *cp; int p[2], c; if (job->mail) user = job->mail; else user = job->person; if (user == NULL) { log_warnx("no user to send report to"); return; } if (pipe(p) == -1) { log_warn("pipe"); return; } switch (fork()) { case -1: (void)close(p[0]); (void)close(p[1]); log_warn("fork"); return; case 0: if (dup2(p[0], 0) == -1) fatal("%s: dup2", __func__); (void)closefrom(3); if ((cp = strrchr(_PATH_SENDMAIL, '/'))) cp++; else cp = _PATH_SENDMAIL; execl(_PATH_SENDMAIL, cp, "-t", (char *)NULL); fatal("%s: execl: %s", __func__, _PATH_SENDMAIL); default: (void)close(p[0]); if ((fp = fdopen(p[1], "w")) == NULL) { (void)close(p[1]); log_warn("fdopen"); return; } } fprintf(fp, "Auto-Submitted: auto-generated\n"); fprintf(fp, "To: %s@%s\n", user, job->host); fprintf(fp, "Subject: %s printer job \"%s\"\n", lp->lp_name, job->name ? job->name : ""); fprintf(fp, "Reply-To: root@%s\n\n", lpd_hostname); fprintf(fp, "Your printer job "); if (job->name) fprintf(fp, " (%s) ", job->name); fprintf(fp, "\n"); switch (result) { case OK: fprintf(fp, "completed successfully"); break; case ERR_ACCOUNT: fprintf(fp, "could not be printed without an account on %s", lpd_hostname); break; case ERR_ACCESS: fprintf(fp, "could not be printed because the file could " " not be read"); break; case ERR_INODE: fprintf(fp, "was not printed because it was not linked to" " the original file"); break; case ERR_NOIMPL: fprintf(fp, "was not printed because some feature is missing"); break; case ERR_FILTER: efp = fopen(prn->efile, "r"); if (efp && fstat(fileno(efp), &st) == 0 && st.st_size) { fprintf(fp, "had the following errors and may not have printed:\n"); while ((c = getc(efp)) != EOF) putc(c, fp); } else fprintf(fp, "had some errors and may not have printed\n"); if (efp) fclose(efp); break; default: printf("could not be printed"); break; } fprintf(fp, "\n"); fclose(fp); wait(NULL); } static void prn_open(void) { const char *status, *oldstatus; int i; switch (lp->lp_type) { case PRN_LOCAL: lp_setstatus(lp, "opening %s", LP_LP(lp)); break; case PRN_NET: case PRN_LPR: lp_setstatus(lp, "connecting to %s:%s", lp->lp_host, lp->lp_port ? lp->lp_port : "printer"); break; } status = oldstatus = NULL; for (i = 0; prn->pfd == -1; i += (i < 6) ? 1 : 0) { if (status != oldstatus) { lp_setstatus(lp, "%s", status); oldstatus = status; } if (i) sleep(1 << i); if ((prn->pfd = prn_connect()) == -1) { status = "waiting for printer to come up"; continue; } if (lp->lp_type == PRN_LPR) { /* Send a recvjob request. */ if (sendcmd("\2%s\n", LP_RP(lp))) { if (errno == 0) log_warnx("remote queue is disabled"); (void)close(prn->pfd); prn->pfd = -1; status = "waiting for queue to be enabled"; } } } switch (lp->lp_type) { case PRN_LOCAL: lp_setstatus(lp, "printing to %s", LP_LP(lp)); break; case PRN_NET: lp_setstatus(lp, "printing to %s:%s", lp->lp_host, lp->lp_port); break; case PRN_LPR: lp_setstatus(lp, "sending to %s", lp->lp_host); break; } prn->tof = lp->lp_fo ? 0 : 1; prn->count = 0; prn_fstart(); } /* * Open the printer device, or connect to the remote host. * Return the printer file desciptor, or -1 on error. */ static int prn_connect(void) { struct addrinfo hints, *res, *res0; int save_errno; int fd, e, mode; const char *cause = NULL, *host, *port; if (lp->lp_type == PRN_LOCAL) { mode = lp->lp_rw ? O_RDWR : O_WRONLY; if ((fd = open(LP_LP(lp), mode)) == -1) { log_warn("failed to open %s", LP_LP(lp)); return -1; } if (isatty(fd)) { lp_stty(lp, fd); return -1; } return fd; } host = lp->lp_host; port = lp->lp_port ? lp->lp_port : "printer"; memset(&hints, 0, sizeof(hints)); hints.ai_family = AF_UNSPEC; hints.ai_socktype = SOCK_STREAM; if ((e = getaddrinfo(host, port, &hints, &res0))) { log_warnx("%s:%s: %s", host, port, gai_strerror(e)); return -1; } fd = -1; for (res = res0; res && fd == -1; res = res->ai_next) { fd = socket(res->ai_family, res->ai_socktype, res->ai_protocol); if (fd == -1) cause = "socket"; else if (connect(fd, res->ai_addr, res->ai_addrlen) == -1) { cause = "connect"; save_errno = errno; (void)close(fd); errno = save_errno; fd = -1; } } if (fd == -1) log_warn("%s", cause); else log_debug("connected to %s:%s", host, port); freeaddrinfo(res0); return fd; } static void prn_close(void) { prn_fclose(); (void)close(prn->pfd); prn->pfd = -1; } /* * Fork the output filter process if needed. */ static int prn_fstart(void) { char width[32], length[32], *cp; int fildes[2], i; if (lp->lp_type == PRN_LPR || (!lp->lp_of)) return 0; pipe(fildes); for (i = 0; i < 20; i++) { if (i) sleep(i); if ((prn->opid = fork()) != -1) break; log_warn("%s: fork", __func__); } if (prn->opid == -1) { log_warnx("cannot fork output filter"); return -1; } if (prn->opid == 0) { /* child */ dup2(fildes[0], 0); dup2(prn->pfd, 1); (void)closefrom(3); cp = strrchr(lp->lp_of, '/'); if (cp) cp += 1; else cp = lp->lp_of; snprintf(width, sizeof(width), "-w%ld", lp->lp_pw); snprintf(length, sizeof(length), "-l%ld", lp->lp_pl); execl(lp->lp_of, cp, width, length, (char *)NULL); log_warn("%s: execl", __func__); exit(1); } close(fildes[0]); prn->ofd = fildes[1]; prn->ofilter = 1; return 0; } /* * Suspend the output filter process. */ static void prn_fsuspend(void) { pid_t pid; int status; if (prn->opid == 0) return; prn_puts("\031\1"); while ((pid = waitpid(WAIT_ANY, &status, WUNTRACED)) && pid != prn->opid) ; prn->ofilter = 0; if (!WIFSTOPPED(status)) { log_warn("output filter died (exitstatus=%d termsig=%d)", WEXITSTATUS(status), WTERMSIG(status)); prn->opid = 0; prn_fclose(); } } /* * Resume the output filter process. */ static void prn_fresume(void) { if (prn->opid == 0) return; if (kill(prn->opid, SIGCONT) == -1) fatal("cannot restart output filter"); prn->ofilter = 1; } /* * Close the output filter socket and wait for the process to terminate * if currently running. */ static void prn_fclose(void) { pid_t pid; close(prn->ofd); prn->ofd = -1; while (prn->opid) { pid = wait(NULL); if (pid == -1) log_warn("%s: wait", __func__); else if (pid == prn->opid) prn->opid = 0; } } /* * Write a form-feed if the printer cap requires it, and if not currently * at top of form. Return 0 on success, or -1 on error and set errno. */ static int prn_formfeed(void) { if (!lp->lp_sf && !prn->tof) if (prn_puts(LP_FF(lp)) == -1) return -1; prn->tof = 1; return 0; } /* * Write data to the printer (or output filter process). * Return 0 on success, or -1 and set errno. */ static int prn_write(const char *buf, size_t len) { ssize_t n; int fd; fd = prn->ofilter ? prn->ofd : prn->pfd; log_debug("prn_write(fd=%d len=%zu, of=%d pfd=%d ofd=%d)", fd, len, prn->ofilter, prn->pfd, prn->ofd); if (fd == -1) { log_warnx("printer socket not opened"); errno = EPIPE; return -1; } while (len) { if ((n = write(fd, buf, len)) == -1) { if (errno == EINTR) continue; log_warn("%s: write", __func__); /* XXX close the printer */ return -1; } len -= n; buf += n; prn->tof = 0; } return 0; } /* * Write a string to the printer (or output filter process). * Return 0 on success, or -1 and set errno. */ static int prn_puts(const char *buf) { return prn_write(buf, strlen(buf)); } /* * Write the FILE content to the printer (or output filter process). * Return 0 on success, or -1 and set errno. */ static int prn_writefile(FILE *fp) { char buf[BUFSIZ]; size_t r; while (!feof(fp)) { r = fread(buf, 1, sizeof(buf), fp); if (ferror(fp)) { log_warn("%s: fread", __func__); return -1; } if (r && (prn_write(buf, r) == -1)) return -1; } return 0; } /* * Read data from the printer socket into the given buffer. * Return 0 on success, or -1 and set errno. */ static ssize_t prn_read(char *buf, size_t sz) { ssize_t n; for (;;) { if ((n = read(prn->pfd, buf, sz)) == 0) { errno = ECONNRESET; n = -1; } if (n == -1) { if (errno == EINTR) continue; /* XXX close printer? */ log_warn("%s: read", __func__); return -1; } return n; } }