/* $NetBSD: lpr.c,v 1.46 2012/06/02 03:32:53 jnemeth Exp $ */ /* * Copyright (c) 1983, 1989, 1993 * The Regents of the University of California. All rights reserved. * (c) UNIX System Laboratories, Inc. * All or some portions of this file are derived from material licensed * to the University of California by American Telephone and Telegraph * Co. or Unix System Laboratories, Inc. and are reproduced herein with * the permission of UNIX System Laboratories, Inc. * * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * 3. Neither the name of the University nor the names of its contributors * may be used to endorse or promote products derived from this software * without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. */ #include #ifndef lint __COPYRIGHT("@(#) Copyright (c) 1983, 1989, 1993\ The Regents of the University of California. All rights reserved."); #if 0 static char sccsid[] = "@(#)lpr.c 8.4 (Berkeley) 4/28/95"; #else __RCSID("$NetBSD: lpr.c,v 1.46 2012/06/02 03:32:53 jnemeth Exp $"); #endif #endif /* not lint */ /* * lpr -- off line print * * Allows multiple printers and printers on remote machines by * using information from a printer data base. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "lp.h" #include "lp.local.h" #include "pathnames.h" static char *cfname; /* daemon control files, linked from tf's */ static char *class = host; /* class title on header page */ static char *dfname; /* data files */ static char *fonts[4]; /* troff font names */ static char format = 'f'; /* format char for printing files */ static int hdr = 1; /* print header or not (default is yes) */ static int iflag; /* indentation wanted */ static int inchar; /* location to increment char in file names */ static int indent; /* amount to indent */ static const char *jobname; /* job name on header page */ static int mailflg; /* send mail */ static int nact; /* number of jobs to act on */ static int ncopies = 1; /* # of copies to make */ static const char *person; /* user name */ static int qflag; /* q job, but don't exec daemon */ static int reqid; /* request id */ static int rflag; /* remove files upon completion */ static int Rflag; /* print request id - like POSIX lp */ static int sflag; /* symbolic link flag */ static int tfd; /* control file descriptor */ static char *tfname; /* tmp copy of cf before linking */ static char *title; /* pr'ing title */ static int userid; /* user id */ static char *width; /* width for versatec printing */ static struct stat statb; static void card(int, const char *); static void chkprinter(const char *); static void cleanup(int) __dead; static void copy(int, const char *); static char *itoa(int); static const char *linked(const char *); static char *lmktemp(const char *, int, int); static void mktemps(void); static int nfile(char *); static int test(const char *); static void usage(void) __dead; uid_t uid, euid; int main(int argc, char *argv[]) { struct passwd *pw; struct group *gptr; char *arg; const char *cp; char buf[MAXPATHLEN]; int i, f, errs, c; struct stat stb; int oerrno; euid = geteuid(); uid = getuid(); seteuid(uid); if (signal(SIGHUP, SIG_IGN) != SIG_IGN) signal(SIGHUP, cleanup); if (signal(SIGINT, SIG_IGN) != SIG_IGN) signal(SIGINT, cleanup); if (signal(SIGQUIT, SIG_IGN) != SIG_IGN) signal(SIGQUIT, cleanup); if (signal(SIGTERM, SIG_IGN) != SIG_IGN) signal(SIGTERM, cleanup); setprogname(*argv); gethostname(host, sizeof (host)); host[sizeof(host) - 1] = '\0'; openlog("lpd", 0, LOG_LPR); errs = 0; while ((c = getopt(argc, argv, ":#:1:2:3:4:C:J:P:RT:U:cdfghi:lmnopqrstvw:")) != -1) { switch (c) { case '#': /* n copies */ if (isdigit((unsigned char)*optarg)) { i = atoi(optarg); if (i > 0) ncopies = i; } break; case '4': /* troff fonts */ case '3': case '2': case '1': fonts[optopt - '1'] = optarg; break; case 'C': /* classification spec */ hdr++; class = optarg; break; case 'J': /* job name */ hdr++; jobname = optarg; break; case 'P': /* specifiy printer name */ printer = optarg; break; case 'R': /* print request id */ Rflag++; break; case 'T': /* pr's title line */ title = optarg; break; case 'U': /* user name */ hdr++; person = optarg; break; case 'c': /* print cifplot output */ case 'd': /* print tex output (dvi files) */ case 'g': /* print graph(1G) output */ case 'l': /* literal output */ case 'o': /* print postscript output */ case 'n': /* print ditroff output */ case 'p': /* print using ``pr'' */ case 't': /* print troff output (cat files) */ case 'v': /* print vplot output */ format = optopt; break; case 'f': /* print fortran output */ format = 'r'; break; case 'h': /* toggle want of header page */ hdr = !hdr; break; case 'i': /* indent output */ iflag++; indent = atoi(optarg); if (indent < 0) indent = 8; break; case 'm': /* send mail when done */ mailflg++; break; case 'q': /* just q job */ qflag++; break; case 'r': /* remove file when done */ rflag++; break; case 's': /* try to link files */ sflag++; break; case 'w': /* versatec page width */ width = optarg; break; case ':': /* catch "missing argument" error */ if (optopt == 'i') { iflag++; /* -i without args is valid */ indent = 8; } else errs++; break; default: errs++; } } argc -= optind; argv += optind; if (errs) usage(); if (printer == NULL && (printer = getenv("PRINTER")) == NULL) printer = DEFLP; chkprinter(printer); if (SC && ncopies > 1) errx(EXIT_FAILURE, "multiple copies are not allowed"); if (MC > 0 && ncopies > MC) errx(EXIT_FAILURE, "only %ld copies are allowed", MC); /* * Get the identity of the person doing the lpr using the same * algorithm as lprm. */ userid = getuid(); if (userid != DU || person == 0) { if ((pw = getpwuid(userid)) == NULL) errx(EXIT_FAILURE, "Who are you?"); person = pw->pw_name; } /* * Check for restricted group access. */ if (RG != NULL && userid != DU) { if ((gptr = getgrnam(RG)) == NULL) errx(EXIT_FAILURE, "Restricted group specified incorrectly"); if (gptr->gr_gid != getgid()) { while (*gptr->gr_mem != NULL) { if ((strcmp(person, *gptr->gr_mem)) == 0) break; gptr->gr_mem++; } if (*gptr->gr_mem == NULL) errx(EXIT_FAILURE, "Not a member of the restricted group"); } } /* * Check to make sure queuing is enabled if userid is not root. */ (void)snprintf(buf, sizeof buf, "%s/%s", SD, LO); if (userid && stat(buf, &stb) == 0 && (stb.st_mode & S_IXGRP)) errx(EXIT_FAILURE, "Printer queue is disabled"); /* * Initialize the control file. */ mktemps(); tfd = nfile(tfname); seteuid(euid); (void)fchown(tfd, DU, -1); /* owned by daemon for protection */ seteuid(uid); card('H', host); card('P', person); if (hdr && !SH) { if (jobname == NULL) { if (argc == 0) jobname = "stdin"; else jobname = (arg = strrchr(argv[0], '/')) ? arg+1 : argv[0]; } card('J', jobname); card('C', class); card('L', person); } if (iflag) card('I', itoa(indent)); if (mailflg) card('M', person); if (format == 't' || format == 'n' || format == 'd') for (i = 0; i < 4; i++) if (fonts[i] != NULL) card('1'+i, fonts[i]); if (width != NULL) card('W', width); /* * Read the files and spool them. */ if (argc == 0) copy(0, " "); else while (argc--) { if (argv[0][0] == '-' && argv[0][1] == '\0') { /* use stdin */ copy(0, " "); argv++; continue; } if ((f = test(arg = *argv++)) < 0) continue; /* file unreasonable */ if (sflag && (cp = linked(arg)) != NULL) { (void)snprintf(buf, sizeof buf, "%llu %llu", (unsigned long long)statb.st_dev, (unsigned long long)statb.st_ino); card('S', buf); if (format == 'p') card('T', title ? title : arg); for (i = 0; i < ncopies; i++) card(format, &dfname[inchar-2]); card('U', &dfname[inchar-2]); if (f) card('U', cp); card('N', arg); dfname[inchar]++; nact++; continue; } if (sflag) warnx("%s: not linked, copying instead", arg); seteuid(uid); if ((i = open(arg, O_RDONLY)) < 0) { oerrno = errno; seteuid(uid); errno = oerrno; warn("cannot open %s", arg); continue; } else { copy(i, arg); (void)close(i); if (f && unlink(arg) < 0) warn("%s: not removed", arg); } seteuid(uid); } if (nact) { (void)close(tfd); tfname[inchar]--; /* * Touch the control file to fix position in the queue. */ seteuid(euid); if ((tfd = open(tfname, O_RDWR)) >= 0) { char ch; if (read(tfd, &ch, 1) == 1 && lseek(tfd, (off_t)0, 0) == 0 && write(tfd, &ch, 1) != 1) { warn("cannot touch %s", tfname); tfname[inchar]++; cleanup(0); } (void)close(tfd); } if (link(tfname, cfname) < 0) { warn("cannot rename %s", cfname); tfname[inchar]++; cleanup(0); } unlink(tfname); seteuid(uid); if (Rflag) printf("request id is %d\n", reqid); if (qflag) /* just queue things up */ exit(0); if (!startdaemon(printer)) printf("jobs queued, but cannot start daemon.\n"); exit(0); } cleanup(0); #ifdef __GNUC__ return (0); #endif /* NOTREACHED */ } /* * Create the file n and copy from file descriptor f. */ static void copy(int f, const char *n) { int fd, i, nr; size_t nc; char buf[BUFSIZ]; if (format == 'p') card('T', title ? title : n); for (i = 0; i < ncopies; i++) card(format, &dfname[inchar-2]); card('U', &dfname[inchar-2]); card('N', n); fd = nfile(dfname); nr = nc = 0; while ((i = read(f, buf, sizeof buf)) > 0) { if (write(fd, buf, i) != i) { warn("%s: temp file write error", n); break; } nc += i; if (nc >= sizeof buf) { nc -= sizeof buf; nr++; if (MX > 0 && nr > MX) { warnx("%s: copy file is too large " "(check :mx:?)\n", n); break; } } } (void)close(fd); if (nc == 0 && nr == 0) printf("%s: %s: empty input file\n", getprogname(), f ? n : "stdin"); else nact++; } /* * Try and link the file to dfname. Return a pointer to the full * path name if successful. */ static const char * linked(const char *file) { char *cp; static char buf[BUFSIZ]; int ret; if (*file != '/') { /* XXX: 2 and file for "/file" */ if (getcwd(buf, sizeof(buf) - 2 - strlen(file)) == NULL) return(NULL); while (file[0] == '.') { switch (file[1]) { case '/': file += 2; continue; case '.': if (file[2] == '/') { if ((cp = strrchr(buf, '/')) != NULL) *cp = '\0'; file += 3; continue; } } break; } strlcat(buf, "/", sizeof(buf)); strlcat(buf, file, sizeof(buf)); file = buf; } seteuid(euid); ret = symlink(file, dfname); seteuid(uid); return(ret ? NULL : file); } /* * Put a line into the control file. */ static void card(int c, const char *p2) { char buf[BUFSIZ]; char *p1 = buf; size_t len = 2; if (strlen(p2) > BUFSIZ - 2) errx(EXIT_FAILURE, "Internal error: String longer than %d", BUFSIZ); *p1++ = c; while ((c = *p2++) != '\0') { *p1++ = (c == '\n') ? ' ' : c; len++; } *p1++ = '\n'; if (write(tfd, buf, len) != (ssize_t)len) warn("Control file write error"); } /* * Create a new file in the spool directory. */ static int nfile(char *n) { int f; int oldumask = umask(0); /* should block signals */ int oerrno; seteuid(euid); f = open(n, O_WRONLY | O_EXCL | O_CREAT, FILMOD); oerrno = errno; (void)umask(oldumask); if (f < 0) { errno = oerrno; warn("cannot create %s", n); cleanup(0); } if (fchown(f, userid, -1) < 0) { warn("cannot chown %s", n); cleanup(0); /* cleanup does exit */ } seteuid(uid); if (++n[inchar] > 'z') { if (++n[inchar-2] == 't') { printf("too many files - break up the job\n"); cleanup(0); } n[inchar] = 'A'; } else if (n[inchar] == '[') n[inchar] = 'a'; return(f); } /* * Cleanup after interrupts and errors. */ static void cleanup(int signo) { int i; signal(SIGHUP, SIG_IGN); signal(SIGINT, SIG_IGN); signal(SIGQUIT, SIG_IGN); signal(SIGTERM, SIG_IGN); i = inchar; seteuid(euid); if (tfname) do unlink(tfname); while (tfname[i]-- != 'A'); if (cfname) do unlink(cfname); while (cfname[i]-- != 'A'); if (dfname) do { do unlink(dfname); while (dfname[i]-- != 'A'); dfname[i] = 'z'; } while (dfname[i-2]-- != 'd'); exit(1); } /* * Test to see if this is a printable file. * Return -1 if it is not, 0 if its printable, and 1 if * we should remove it after printing. */ static int test(const char *file) { int fd; char *cp; seteuid(uid); if (access(file, 4) < 0) { warn("cannot access %s", file); goto bad; } if (stat(file, &statb) < 0) { warn("cannot stat %s", file); goto bad; } if (S_ISDIR(statb.st_mode)) { warnx("%s is a directory", file); goto bad; } if (statb.st_size == 0) { warnx("%s is an empty file", file); goto bad; } if ((fd = open(file, O_RDONLY)) < 0) { warn("cannot open %s", file); goto bad; } (void)close(fd); if (rflag) { if ((cp = strrchr(file, '/')) == NULL) { if (access(".", 2) == 0) return(1); } else { if (cp == file) { fd = access("/", 2); } else { *cp = '\0'; fd = access(file, 2); *cp = '/'; } if (fd == 0) return(1); } warnx("%s: is not removable by you", file); } return(0); bad: seteuid(uid); return(-1); } /* * itoa - integer to string conversion */ static char * itoa(int i) { static char b[10] = "########"; char *p; p = &b[8]; do *p-- = i%10 + '0'; while (i /= 10); return(++p); } /* * Perform lookup for printer name or abbreviation -- */ static void chkprinter(const char *s) { char *cp; getprintcap(s); RG = cgetstr(bp, "rg", &cp) == -1 ? NULL : cp; if (cgetnum(bp, "mx", &MX) < 0) MX = DEFMX; if (cgetnum(bp,"mc", &MC) < 0) MC = DEFMAXCOPIES; if (cgetnum(bp, "du", &DU) < 0) DU = DEFUID; SC = (cgetcap(bp, "sc", ':') != NULL); } /* * Make the temp files. */ static void mktemps(void) { int len, fd, n; char *cp; char buf[MAXPATHLEN]; (void)snprintf(buf, sizeof(buf), "%s/.seq", SD); seteuid(euid); if ((fd = open(buf, O_RDWR|O_CREAT, 0664)) < 0) err(1, "cannot create %s", buf); if (flock(fd, LOCK_EX)) err(1, "cannot lock %s", buf); seteuid(uid); n = 0; if ((len = read(fd, buf, sizeof(buf))) > 0) { for (cp = buf; len--; ) { if (*cp < '0' || *cp > '9') break; n = n * 10 + (*cp++ - '0'); } } reqid = n; len = strlen(SD) + strlen(host) + 8; tfname = lmktemp("tf", n, len); cfname = lmktemp("cf", n, len); dfname = lmktemp("df", n, len); inchar = strlen(SD) + 3; n = (n + 1) % 1000; (void)lseek(fd, (off_t)0, 0); (void)snprintf(buf, sizeof(buf), "%03d\n", n); (void)write(fd, buf, strlen(buf)); (void)close(fd); /* unlocks as well */ } /* * Make a temp file name. */ static char * lmktemp(const char *id, int num, int len) { char *s; if ((s = malloc(len)) == NULL) err(EXIT_FAILURE, NULL); (void)snprintf(s, len, "%s/%sA%03d%s", SD, id, num, host); return(s); } static void usage(void) { fprintf(stderr, "Usage: %s [-Pprinter] [-#num] [-C class] [-J job] [-T title] " "[-U user]\n" "%s [-i[numcols]] [-1234 font] [-wnum] [-cdfghlmnopqRrstv] " "[name ...]\n", getprogname(), getprogname()); exit(1); }