1/*	$OpenBSD: lpr.c,v 1.50 2022/12/28 21:30:17 jmc Exp $ */
2/*	$NetBSD: lpr.c,v 1.19 2000/10/11 20:23:52 is Exp $	*/
3
4/*
5 * Copyright (c) 1983, 1989, 1993
6 *	The Regents of the University of California.  All rights reserved.
7 * (c) UNIX System Laboratories, Inc.
8 * All or some portions of this file are derived from material licensed
9 * to the University of California by American Telephone and Telegraph
10 * Co. or Unix System Laboratories, Inc. and are reproduced herein with
11 * the permission of UNIX System Laboratories, Inc.
12 *
13 *
14 * Redistribution and use in source and binary forms, with or without
15 * modification, are permitted provided that the following conditions
16 * are met:
17 * 1. Redistributions of source code must retain the above copyright
18 *    notice, this list of conditions and the following disclaimer.
19 * 2. Redistributions in binary form must reproduce the above copyright
20 *    notice, this list of conditions and the following disclaimer in the
21 *    documentation and/or other materials provided with the distribution.
22 * 3. Neither the name of the University nor the names of its contributors
23 *    may be used to endorse or promote products derived from this software
24 *    without specific prior written permission.
25 *
26 * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
27 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
28 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
29 * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
30 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
31 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
32 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
33 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
34 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
35 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
36 * SUCH DAMAGE.
37 */
38
39/*
40 *      lpr -- off line print
41 *
42 * Allows multiple printers and printers on remote machines by
43 * using information from a printer data base.
44 */
45
46#include <sys/stat.h>
47
48#include <dirent.h>
49#include <errno.h>
50#include <fcntl.h>
51#include <signal.h>
52#include <syslog.h>
53#include <pwd.h>
54#include <grp.h>
55#include <unistd.h>
56#include <limits.h>
57#include <stdlib.h>
58#include <stdio.h>
59#include <ctype.h>
60#include <string.h>
61#include <err.h>
62
63#include "lp.h"
64#include "lp.local.h"
65#include "pathnames.h"
66
67static char	*cfname;	/* daemon control files, linked from tf's */
68static char	*class = host;	/* class title on header page */
69static char	*dfname;	/* data files */
70static char	*fonts[4];	/* troff font names */
71static char	 format = 'f';	/* format char for printing files */
72static int	 hdr = 1;	/* print header or not (default is yes) */
73static int	 iflag;		/* indentation wanted */
74static int	 inchar;	/* location to increment char in file names */
75static int	 indent;	/* amount to indent */
76static char	*jobname;	/* job name on header page */
77static int	 mailflg;	/* send mail */
78static int	 nact;		/* number of jobs to act on */
79static int	 ncopies = 1;	/* # of copies to make */
80static const char *person;	/* user name */
81static int	 qflag;		/* q job, but don't exec daemon */
82static int	 rflag;		/* remove files upon completion */
83static int	 sflag;		/* symbolic link flag */
84static int	 tfd;		/* control file descriptor */
85static char	*tfname;	/* tmp copy of cf before linking */
86static char	*title;		/* pr'ing title */
87static char	*width;		/* width for versatec printing */
88
89static struct stat statb;
90
91volatile sig_atomic_t gotintr;
92
93static void	 card(int, const char *);
94static void	 chkprinter(char *);
95static void	 cleanup(int);
96static void	 copy(int, char *);
97static char	*itoa(int);
98static char	*linked(char *);
99static char	*lmktemp(char *, int);
100static void	 mktemps(void);
101static int	 nfile(char *);
102static int	 test(char *);
103static __dead void usage(void);
104
105int
106main(int argc, char **argv)
107{
108	struct passwd *pw;
109	struct group *gptr;
110	char *arg, *cp;
111	char buf[PATH_MAX];
112	int i, f, ch;
113	struct stat stb;
114
115	/*
116	 * Simulate setuid daemon w/ PRIV_END called.
117	 * We don't want lpr to actually be setuid daemon since that
118	 * requires that the lpr binary be owned by user daemon, which
119	 * is potentially unsafe.
120	 */
121	if ((pw = getpwuid(DEFUID)) == NULL)
122		errx(1, "daemon uid (%u) not in password file", DEFUID);
123	effective_uid = pw->pw_uid;
124	real_uid = getuid();
125	effective_gid = pw->pw_gid;
126	real_gid = getgid();
127	setresgid(real_gid, real_gid, effective_gid);
128	setresuid(real_uid, real_uid, effective_uid);
129
130	if (signal(SIGHUP, SIG_IGN) != SIG_IGN)
131		signal(SIGHUP, cleanup);
132	if (signal(SIGINT, SIG_IGN) != SIG_IGN)
133		signal(SIGINT, cleanup);
134	if (signal(SIGQUIT, SIG_IGN) != SIG_IGN)
135		signal(SIGQUIT, cleanup);
136	if (signal(SIGTERM, SIG_IGN) != SIG_IGN)
137		signal(SIGTERM, cleanup);
138
139	gethostname(host, sizeof (host));
140	openlog("lpr", 0, LOG_LPR);
141
142	while ((ch = getopt(argc, argv,
143	    ":#:1:2:3:4:C:J:P:T:U:cdfghi:lmnpqrstvw:")) != -1) {
144		switch (ch) {
145
146		case '#':		/* n copies */
147			if (isdigit((unsigned char)*optarg)) {
148				i = atoi(optarg);
149				if (i > 0)
150					ncopies = i;
151			}
152			break;
153
154		case '4':		/* troff fonts */
155		case '3':
156		case '2':
157		case '1':
158			fonts[ch - '1'] = optarg;
159			break;
160
161		case 'C':		/* classification spec */
162			hdr++;
163			class = optarg;
164			break;
165
166		case 'J':		/* job name */
167			hdr++;
168			jobname = optarg;
169			break;
170
171		case 'P':		/* specify printer name */
172			printer = optarg;
173			break;
174
175		case 'T':		/* pr's title line */
176			title = optarg;
177			break;
178
179		case 'U':		/* user name */
180			hdr++;
181			person = optarg;
182			break;
183
184		case 'c':		/* print cifplot output */
185		case 'd':		/* print tex output (dvi files) */
186		case 'g':		/* print graph(1G) output */
187		case 'l':		/* literal output */
188		case 'n':		/* print ditroff output */
189		case 'p':		/* print using ``pr'' */
190		case 't':		/* print troff output (cat files) */
191		case 'v':		/* print vplot output */
192			format = ch;
193			break;
194
195		case 'f':		/* print fortran output */
196			format = 'r';
197			break;
198
199		case 'h':		/* toggle want of header page */
200			hdr = !hdr;
201			break;
202
203		case 'i':		/* indent output */
204			iflag++;
205			indent = atoi(optarg);
206			if (indent < 0)
207				indent = 8;
208			break;
209
210		case 'm':		/* send mail when done */
211			mailflg = 1;
212			break;
213
214		case 'q':		/* just q job */
215			qflag = 1;
216			break;
217
218		case 'r':		/* remove file when done */
219			rflag = 1;
220			break;
221
222		case 's':		/* try to link files */
223			sflag = 1;
224			break;
225
226		case 'w':		/* versatec page width */
227			width = optarg;
228			break;
229
230		case ':':               /* catch "missing argument" error */
231			if (optopt == 'i') {
232				iflag++; /* -i without args is valid */
233				indent = 8;
234			} else
235				usage();
236			break;
237
238		default:
239			usage();
240		}
241	}
242	argc -= optind;
243	argv += optind;
244	if (printer == NULL && (printer = getenv("PRINTER")) == NULL)
245		printer = DEFLP;
246	chkprinter(printer);
247	if (SC && ncopies > 1)
248		errx(1, "multiple copies are not allowed");
249	if (MC > 0 && ncopies > MC)
250		errx(1, "only %ld copies are allowed", MC);
251	/*
252	 * Get the identity of the person doing the lpr using the same
253	 * algorithm as lprm.
254	 */
255	if (real_uid != DU || person == NULL) {
256		if ((pw = getpwuid(real_uid)) == NULL)
257			errx(1, "Who are you?");
258		if ((person = strdup(pw->pw_name)) == NULL)
259			err(1, NULL);
260	}
261	/*
262	 * Check for restricted group access.
263	 */
264	if (RG != NULL && real_uid != DU) {
265		if ((gptr = getgrnam(RG)) == NULL)
266			errx(1, "Restricted group specified incorrectly");
267		if (gptr->gr_gid != getgid()) {
268			while (*gptr->gr_mem != NULL) {
269				if ((strcmp(person, *gptr->gr_mem)) == 0)
270					break;
271				gptr->gr_mem++;
272			}
273			if (*gptr->gr_mem == NULL)
274				errx(1, "Not a member of the restricted group");
275		}
276	}
277	/*
278	 * Check to make sure queuing is enabled if real_uid is not root.
279	 */
280	(void)snprintf(buf, sizeof(buf), "%s/%s", SD, LO);
281	if (real_uid && stat(buf, &stb) == 0 && (stb.st_mode & 010))
282		errx(1, "Printer queue is disabled");
283	/*
284	 * Initialize the control file.
285	 */
286	mktemps();
287	tfd = nfile(tfname);
288	card('H', host);
289	card('P', person);
290	if (hdr) {
291		if (jobname == NULL) {
292			if (argc == 0)
293				jobname = "stdin";
294			else
295				jobname = (arg = strrchr(argv[0], '/')) ?
296				    arg + 1 : argv[0];
297		}
298		card('J', jobname);
299		card('C', class);
300		if (!SH)
301			card('L', person);
302	}
303	if (iflag)
304		card('I', itoa(indent));
305	if (mailflg)
306		card('M', person);
307	if (format == 't' || format == 'n' || format == 'd')
308		for (i = 0; i < 4; i++)
309			if (fonts[i] != NULL)
310				card('1'+i, fonts[i]);
311	if (width != NULL)
312		card('W', width);
313
314	/*
315	 * Read the files and spool them.
316	 */
317	if (argc == 0)
318		copy(0, " ");
319	else while (argc--) {
320		if (argv[0][0] == '-' && argv[0][1] == '\0') {
321			/* use stdin */
322			copy(0, " ");
323			argv++;
324			continue;
325		}
326		if ((f = test(arg = *argv++)) < 0)
327			continue;	/* file unreasonable */
328
329		if (sflag && (cp = linked(arg)) != NULL) {
330			(void)snprintf(buf, sizeof(buf), "%d %llu",
331			    statb.st_dev, (unsigned long long)statb.st_ino);
332			card('S', buf);
333			if (format == 'p')
334				card('T', title ? title : arg);
335			for (i = 0; i < ncopies; i++)
336				card(format, &dfname[inchar-2]);
337			card('U', &dfname[inchar-2]);
338			if (f)
339				card('U', cp);
340			card('N', arg);
341			dfname[inchar]++;
342			nact++;
343			continue;
344		}
345		if (sflag)
346			warnx("%s: not linked, copying instead", arg);
347		if ((i = safe_open(arg, O_RDONLY, 0)) < 0)
348			warn("%s", arg);
349		else {
350			copy(i, arg);
351			(void)close(i);
352			if (f && unlink(arg) < 0)
353				warnx("%s: not removed", arg);
354		}
355	}
356
357	if (nact) {
358		(void)close(tfd);
359		tfname[inchar]--;
360		/*
361		 * Touch the control file to fix position in the queue.
362		 */
363		PRIV_START;
364		if ((tfd = safe_open(tfname, O_RDWR|O_NOFOLLOW, 0)) >= 0) {
365			char c;
366
367			if (read(tfd, &c, 1) == 1 &&
368			    lseek(tfd, (off_t)0, SEEK_SET) == 0 &&
369			    write(tfd, &c, 1) != 1) {
370				warn("%s", tfname);
371				tfname[inchar]++;
372				cleanup(0);
373			}
374			(void)close(tfd);
375		}
376		if (link(tfname, cfname) < 0) {
377			warn("cannot rename %s", cfname);
378			tfname[inchar]++;
379			cleanup(0);
380		}
381		unlink(tfname);
382		PRIV_END;
383		if (qflag)		/* just q things up */
384			exit(0);
385		if (!startdaemon(printer))
386			printf("jobs queued, but cannot start daemon.\n");
387		exit(0);
388	}
389	cleanup(0);
390	return (1);
391	/* NOTREACHED */
392}
393
394/*
395 * Create the file n and copy from file descriptor f.
396 */
397static void
398copy(int f, char *n)
399{
400	int fd, i, nr, nc;
401	char buf[BUFSIZ];
402
403	if (format == 'p')
404		card('T', title ? title : n);
405	for (i = 0; i < ncopies; i++)
406		card(format, &dfname[inchar-2]);
407	card('U', &dfname[inchar-2]);
408	card('N', n);
409	fd = nfile(dfname);
410	nr = nc = 0;
411	while ((i = read(f, buf, sizeof(buf))) > 0) {
412		if (write(fd, buf, i) != i) {
413			warn("%s", n);
414			break;
415		}
416		nc += i;
417		if (nc >= sizeof(buf)) {
418			nc -= sizeof(buf);
419			nr++;
420			if (MX > 0 && nr > MX) {
421				warnx("%s: copy file is too large", n);
422				break;
423			}
424		}
425	}
426	(void)close(fd);
427	if (nc == 0 && nr == 0)
428		warnx("%s: empty input file", f ? n : "stdin");
429	else
430		nact++;
431}
432
433/*
434 * Try and link the file to dfname. Return a pointer to the full
435 * path name if successful.
436 */
437static char *
438linked(char *file)
439{
440	char *cp;
441	static char buf[PATH_MAX];
442	int ret;
443
444	if (*file != '/') {
445		if (getcwd(buf, sizeof(buf)) == NULL)
446			return(NULL);
447
448		while (file[0] == '.') {
449			switch (file[1]) {
450			case '/':
451				file += 2;
452				continue;
453			case '.':
454				if (file[2] == '/') {
455					if ((cp = strrchr(buf, '/')) != NULL)
456						*cp = '\0';
457					file += 3;
458					continue;
459				}
460			}
461			break;
462		}
463		if (strlcat(buf, "/", sizeof(buf)) >= sizeof(buf) ||
464		    strlcat(buf, file, sizeof(buf)) >= sizeof(buf))
465			return(NULL);
466		file = buf;
467	}
468	PRIV_START;
469	ret = symlink(file, dfname);
470	PRIV_END;
471	return(ret ? NULL : file);
472}
473
474/*
475 * Put a line into the control file.
476 */
477static void
478card(int c, const char *p2)
479{
480	char buf[BUFSIZ];
481	char *p1 = buf;
482	int len = 2;
483
484	if (strlen(p2) > sizeof(buf) - 2)
485		errx(1, "Internal error:  String longer than %ld",
486		    (long)sizeof(buf));
487
488	*p1++ = c;
489	while ((c = *p2++) != '\0' && len < sizeof(buf)) {
490		*p1++ = (c == '\n') ? ' ' : c;
491		len++;
492	}
493	*p1++ = '\n';
494	write(tfd, buf, len);
495}
496
497/*
498 * Create a new file in the spool directory.
499 */
500static int
501nfile(char *n)
502{
503	int f;
504	int oldumask = umask(0);		/* should block signals */
505
506	PRIV_START;
507	f = open(n, O_WRONLY|O_EXCL|O_CREAT, FILMOD);
508	(void)umask(oldumask);
509	if (f < 0) {
510		warn("%s", n);
511		cleanup(0);
512	}
513	PRIV_END;
514	if (++n[inchar] > 'z') {
515		if (++n[inchar-2] == 't') {
516			warnx("too many files - break up the job");
517			cleanup(0);
518		}
519		n[inchar] = 'A';
520	} else if (n[inchar] == '[')
521		n[inchar] = 'a';
522	return (f);
523}
524
525/*
526 * Cleanup after interrupts and errors.
527 */
528static void
529cleanup(int signo)
530{
531	int i;
532
533	signal(SIGHUP, SIG_IGN);
534	signal(SIGINT, SIG_IGN);
535	signal(SIGQUIT, SIG_IGN);
536	signal(SIGTERM, SIG_IGN);
537	i = inchar;
538	PRIV_START;
539	if (tfname)
540		do
541			unlink(tfname);
542		while (tfname[i]-- != 'A');
543	if (cfname)
544		do
545			unlink(cfname);
546		while (cfname[i]-- != 'A');
547	if (dfname)
548		do {
549			do
550				unlink(dfname);
551			while (dfname[i]-- != 'A');
552			dfname[i] = 'z';
553		} while (dfname[i-2]-- != 'd');
554	_exit(1);
555}
556
557/*
558 * Test to see if this is a printable file.
559 * Return -1 if it is not, 0 if its printable, and 1 if
560 * we should remove it after printing.
561 */
562static int
563test(char *file)
564{
565	int fd;
566	char *cp;
567
568	if ((fd = open(file, O_RDONLY|O_NONBLOCK)) < 0) {
569		warn("cannot open %s", file);
570		goto bad;
571	}
572	if (fstat(fd, &statb) < 0) {
573		warn("cannot stat %s", file);
574		goto bad;
575	}
576	if (S_ISDIR(statb.st_mode)) {
577		warnx("%s is a directory", file);
578		goto bad;
579	}
580	if (!S_ISREG(statb.st_mode)) {
581		warnx("%s is not a regular file", file);
582		goto bad;
583	}
584	if (statb.st_size == 0) {
585		warnx("%s is an empty file", file);
586		goto bad;
587 	}
588	(void)close(fd);
589	if (rflag) {
590		if ((cp = strrchr(file, '/')) == NULL) {
591			if (access(".", 2) == 0)
592				return(1);
593		} else {
594			if (cp == file) {
595				fd = access("/", 2);
596			} else {
597				*cp = '\0';
598				fd = access(file, 2);
599				*cp = '/';
600			}
601			if (fd == 0)
602				return(1);
603		}
604		warnx("%s is not removable by you", file);
605	}
606	return(0);
607bad:
608	return(-1);
609}
610
611/*
612 * itoa - integer to string conversion
613 */
614static char *
615itoa(int i)
616{
617	static char b[10] = "########";
618	char *p;
619
620	p = &b[8];
621	do
622		*p-- = i%10 + '0';
623	while (i /= 10)
624		;
625	return(++p);
626}
627
628/*
629 * Perform lookup for printer name or abbreviation --
630 */
631static void
632chkprinter(char *s)
633{
634	int status;
635
636	if ((status = cgetent(&bp, printcapdb, s)) == -2)
637		errx(1, "cannot open printer description file");
638	else if (status == -1)
639		errx(1, "%s: unknown printer", s);
640	if (cgetstr(bp, "sd", &SD) == -1)
641		SD = _PATH_DEFSPOOL;
642	if (cgetstr(bp, "lo", &LO) == -1)
643		LO = DEFLOCK;
644	cgetstr(bp, "rg", &RG);
645	if (cgetnum(bp, "mx", &MX) < 0)
646		MX = DEFMX;
647	if (cgetnum(bp, "mc", &MC) < 0)
648		MC = DEFMAXCOPIES;
649	if (cgetnum(bp, "du", &DU) < 0)
650		DU = DEFUID;
651	SC = (cgetcap(bp, "sc", ':') != NULL);
652	SH = (cgetcap(bp, "sh", ':') != NULL);
653}
654
655/*
656 * Make the temp files.
657 */
658static void
659mktemps(void)
660{
661	int len, fd, n;
662	char *cp;
663	char buf[BUFSIZ];
664	struct stat stb;
665
666	if (snprintf(buf, sizeof(buf), "%s/.seq", SD) >= sizeof(buf))
667		errc(1, ENAMETOOLONG, "%s/.seq", SD);
668	PRIV_START;
669	if ((fd = safe_open(buf, O_RDWR|O_CREAT|O_NOFOLLOW, 0661)) < 0)
670		err(1, "cannot open %s", buf);
671	if (flock(fd, LOCK_EX))
672		err(1, "cannot lock %s", buf);
673	PRIV_END;
674	n = 0;
675	if ((len = read(fd, buf, sizeof(buf))) > 0) {
676		for (cp = buf; len--; ) {
677			if (*cp < '0' || *cp > '9')
678				break;
679			n = n * 10 + (*cp++ - '0');
680		}
681	}
682	do {
683		tfname = lmktemp("tf", n);
684		cfname = lmktemp("cf", n);
685		dfname = lmktemp("df", n);
686		n = (n + 1) % 1000;
687	} while (stat(tfname, &stb) == 0 || stat(cfname, &stb) == 0 ||
688	    stat(dfname, &stb) == 0);
689	inchar = strlen(SD) + 3;
690	(void)lseek(fd, (off_t)0, SEEK_SET);
691	snprintf(buf, sizeof(buf), "%03d\n", n);
692	(void)write(fd, buf, strlen(buf));
693	(void)close(fd);	/* unlocks as well */
694}
695
696/*
697 * Make a temp file name.
698 */
699static char *
700lmktemp(char *id, int num)
701{
702	char *s;
703
704	if (asprintf(&s, "%s/%sA%03d%s", SD, id, num, host) == -1)
705		err(1, NULL);
706
707	return(s);
708}
709
710static __dead void
711usage(void)
712{
713	extern char *__progname;
714
715	fprintf(stderr,
716	    "usage: %s [-cdfghlmnpqrstv] [-#num] [-1234 font] "
717	    "[-C class] [-i [numcols]]\n"
718	    "\t[-J job] [-Pprinter] [-T title] [-U user] "
719	    "[-wnum] [name ...]\n", __progname);
720	exit(1);
721}
722