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