1/*-
2 * SPDX-License-Identifier: BSD-4-Clause
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. All advertising materials mentioning features or use of this software
22 *    must display the following acknowledgement:
23 *	This product includes software developed by the University of
24 *	California, Berkeley and its contributors.
25 * 4. Neither the name of the University nor the names of its contributors
26 *    may be used to endorse or promote products derived from this software
27 *    without specific prior written permission.
28 *
29 * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
30 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
31 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
32 * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
33 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
34 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
35 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
36 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
37 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
38 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
39 * SUCH DAMAGE.
40 */
41
42#include "lp.cdefs.h"		/* A cross-platform version of <sys/cdefs.h> */
43/*
44 *      lpr -- off line print
45 *
46 * Allows multiple printers and printers on remote machines by
47 * using information from a printer data base.
48 */
49
50#include <sys/param.h>
51#include <sys/stat.h>
52
53#include <netinet/in.h>		/* N_BADMAG uses ntohl() */
54
55#include <dirent.h>
56#include <fcntl.h>
57#include <err.h>
58#include <locale.h>
59#include <signal.h>
60#include <syslog.h>
61#include <pwd.h>
62#include <grp.h>
63#include <unistd.h>
64#include <stdlib.h>
65#include <stdint.h>
66#include <stdio.h>
67#include <ctype.h>
68#include <string.h>
69#include "lp.h"
70#include "lp.local.h"
71#include "pathnames.h"
72
73static char	*cfname;	/* daemon control files, linked from tf's */
74static char	*class = local_host;	/* class title on header page */
75static char	*dfname;	/* data files */
76static char	*fonts[4];	/* troff font names */
77static char	 format = 'f';	/* format char for printing files */
78static int	 hdr = 1;	/* print header or not (default is yes) */
79static int	 iflag;		/* indentation wanted */
80static int	 inchar;	/* location to increment char in file names */
81static int	 indent;	/* amount to indent */
82static const char *jobname;	/* job name on header page */
83static int	 mailflg;	/* send mail */
84static int	 nact;		/* number of jobs to act on */
85static int	 ncopies = 1;	/* # of copies to make */
86static char	*lpr_username;  /* person sending the print job(s) */
87static int	 qflag;		/* q job, but don't exec daemon */
88static int	 rflag;		/* remove files upon completion */
89static int	 sflag;		/* symbolic link flag */
90static int	 tfd;		/* control file descriptor */
91static char	*tfname;	/* tmp copy of cf before linking */
92static char	*title;		/* pr'ing title */
93static char     *locale;        /* pr'ing locale */
94static int	 userid;	/* user id */
95static char	*Uflag;		/* user name specified with -U flag */
96static char	*width;		/* width for versatec printing */
97static char	*Zflag;		/* extra filter options for LPRng servers */
98
99static struct stat statb;
100
101static void	 card(int _c, const char *_p2);
102static int	 checkwriteperm(const char *_file, const char *_directory);
103static void	 chkprinter(const char *_ptrname, struct printer *_pp);
104static void	 cleanup(int _signo);
105static void	 copy(const struct printer *_pp, int _f, const char _n[]);
106static char	*itoa(int _i);
107static const char  *linked(const char *_file);
108int		 main(int _argc, char *_argv[]);
109static char	*lmktemp(const struct printer *_pp, const char *_id,
110		    int _num, int len);
111static void	 mktemps(const struct printer *_pp);
112static int	 nfile(char *_n);
113static int	 test(const char *_file);
114static void	 usage(void);
115
116uid_t	uid, euid;
117
118int
119main(int argc, char *argv[])
120{
121	struct passwd *pw;
122	struct group *gptr;
123	const char *arg, *cp, *printer;
124	char *p;
125	char buf[BUFSIZ];
126	int c, i, f, errs;
127	int	 ret, didlink;
128	struct stat stb;
129	struct stat statb1, statb2;
130	struct printer myprinter, *pp = &myprinter;
131
132	printer = NULL;
133	euid = geteuid();
134	uid = getuid();
135	PRIV_END
136	if (signal(SIGHUP, SIG_IGN) != SIG_IGN)
137		signal(SIGHUP, cleanup);
138	if (signal(SIGINT, SIG_IGN) != SIG_IGN)
139		signal(SIGINT, cleanup);
140	if (signal(SIGQUIT, SIG_IGN) != SIG_IGN)
141		signal(SIGQUIT, cleanup);
142	if (signal(SIGTERM, SIG_IGN) != SIG_IGN)
143		signal(SIGTERM, cleanup);
144
145	progname = argv[0];
146	gethostname(local_host, sizeof(local_host));
147	openlog("lpd", 0, LOG_LPR);
148
149	errs = 0;
150	while ((c = getopt(argc, argv,
151			   ":#:1:2:3:4:C:J:L:P:T:U:Z:cdfghi:lnmprstvw:"))
152	       != -1)
153		switch (c) {
154		case '#':		/* n copies */
155			i = strtol(optarg, &p, 10);
156			if (*p)
157				errx(1, "Bad argument to -#, number expected");
158			if (i > 0)
159				ncopies = i;
160			break;
161
162		case '1':		/* troff fonts */
163		case '2':
164		case '3':
165		case '4':
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 'L':               /* pr's locale */
184			locale = optarg;
185			break;
186
187		case 'T':		/* pr's title line */
188			title = optarg;
189			break;
190
191		case 'U':		/* user name */
192			hdr++;
193			Uflag = optarg;
194			break;
195
196		case 'Z':
197			Zflag = optarg;
198			break;
199
200		case 'c':		/* print cifplot output */
201		case 'd':		/* print tex output (dvi files) */
202		case 'g':		/* print graph(1G) output */
203		case 'l':		/* literal output */
204		case 'n':		/* print ditroff output */
205		case 't':		/* print troff output (cat files) */
206		case 'p':		/* print using ``pr'' */
207		case 'v':		/* print vplot output */
208			format = optopt;
209			break;
210
211		case 'f':		/* print fortran output */
212			format = 'r';
213			break;
214
215		case 'h':		/* nulifiy header page */
216			hdr = 0;
217			break;
218
219		case 'i':		/* indent output */
220			iflag++;
221			indent = strtol(optarg, &p, 10);
222			if (*p)
223				errx(1, "Bad argument to -i, number expected");
224			break;
225
226		case 'm':		/* send mail when done */
227			mailflg++;
228			break;
229
230		case 'q':		/* just queue job */
231			qflag++;
232			break;
233
234		case 'r':		/* remove file when done */
235			rflag++;
236			break;
237
238		case 's':		/* try to link files */
239			sflag++;
240			break;
241
242		case 'w':		/* versatec page width */
243			width = optarg;
244			break;
245
246		case ':':		/* catch "missing argument" error */
247			if (optopt == 'i') {
248				iflag++; /* -i without args is valid */
249				indent = 8;
250			} else
251				errs++;
252			break;
253
254		default:
255			errs++;
256		}
257	argc -= optind;
258	argv += optind;
259	if (errs)
260		usage();
261	if (printer == NULL && (printer = getenv("PRINTER")) == NULL)
262		printer = DEFLP;
263	chkprinter(printer, pp);
264	if (pp->no_copies && ncopies > 1)
265		errx(1, "multiple copies are not allowed");
266	if (pp->max_copies > 0 && ncopies > pp->max_copies)
267		errx(1, "only %ld copies are allowed", pp->max_copies);
268	/*
269	 * Get the identity of the person doing the lpr using the same
270	 * algorithm as lprm.  Actually, not quite -- lprm will override
271	 * the login name with "root" if the user is running as root;
272	 * the daemon actually checks for the string "root" in its
273	 * permission checking.  Sigh.
274	 */
275	userid = getuid();
276	if (Uflag) {
277		if (userid != 0 && userid != pp->daemon_user)
278			errx(1, "only privileged users may use the `-U' flag");
279		lpr_username = Uflag;		/* -U person doing 'lpr' */
280	} else {
281		lpr_username = getlogin();	/* person doing 'lpr' */
282		if (userid != pp->daemon_user || lpr_username == 0) {
283			if ((pw = getpwuid(userid)) == NULL)
284				errx(1, "Who are you?");
285			lpr_username = pw->pw_name;
286		}
287	}
288
289	/*
290	 * Check for restricted group access.
291	 */
292	if (pp->restrict_grp != NULL && userid != pp->daemon_user) {
293		if ((gptr = getgrnam(pp->restrict_grp)) == NULL)
294			errx(1, "Restricted group specified incorrectly");
295		if (gptr->gr_gid != getgid()) {
296			while (*gptr->gr_mem != NULL) {
297				if ((strcmp(lpr_username, *gptr->gr_mem)) == 0)
298					break;
299				gptr->gr_mem++;
300			}
301			if (*gptr->gr_mem == NULL)
302				errx(1, "Not a member of the restricted group");
303		}
304	}
305	/*
306	 * Check to make sure queuing is enabled if userid is not root.
307	 */
308	lock_file_name(pp, buf, sizeof buf);
309	if (userid && stat(buf, &stb) == 0 && (stb.st_mode & LFM_QUEUE_DIS))
310		errx(1, "Printer queue is disabled");
311	/*
312	 * Initialize the control file.
313	 */
314	mktemps(pp);
315	tfd = nfile(tfname);
316	PRIV_START
317	(void) fchown(tfd, pp->daemon_user, -1);
318	/* owned by daemon for protection */
319	PRIV_END
320	card('H', local_host);
321	card('P', lpr_username);
322	card('C', class);
323	if (hdr && !pp->no_header) {
324		if (jobname == NULL) {
325			if (argc == 0)
326				jobname = "stdin";
327			else
328				jobname = ((arg = strrchr(argv[0], '/'))
329					   ? arg + 1 : argv[0]);
330		}
331		card('J', jobname);
332		card('L', lpr_username);
333	}
334	if (format != 'p' && Zflag != 0)
335		card('Z', Zflag);
336	if (iflag)
337		card('I', itoa(indent));
338	if (mailflg)
339		card('M', lpr_username);
340	if (format == 't' || format == 'n' || format == 'd')
341		for (i = 0; i < 4; i++)
342			if (fonts[i] != NULL)
343				card('1'+i, fonts[i]);
344	if (width != NULL)
345		card('W', width);
346	/*
347	 * XXX
348	 * Our use of `Z' here is incompatible with LPRng's
349	 * use.  We assume that the only use of our existing
350	 * `Z' card is as shown for `p' format (pr) files.
351	 */
352	if (format == 'p') {
353		char *s;
354
355		if (locale)
356			card('Z', locale);
357		else if ((s = setlocale(LC_TIME, "")) != NULL)
358			card('Z', s);
359	}
360
361	/*
362	 * Read the files and spool them.
363	 */
364	if (argc == 0)
365		copy(pp, 0, " ");
366	else while (argc--) {
367		if (argv[0][0] == '-' && argv[0][1] == '\0') {
368			/* use stdin */
369			copy(pp, 0, " ");
370			argv++;
371			continue;
372		}
373		if ((f = test(arg = *argv++)) < 0)
374			continue;	/* file unreasonable */
375
376		if (sflag && (cp = linked(arg)) != NULL) {
377			(void)snprintf(buf, sizeof(buf), "%ju %ju",
378			    (uintmax_t)statb.st_dev, (uintmax_t)statb.st_ino);
379			card('S', buf);
380			if (format == 'p')
381				card('T', title ? title : arg);
382			for (i = 0; i < ncopies; i++)
383				card(format, &dfname[inchar-2]);
384			card('U', &dfname[inchar-2]);
385			if (f)
386				card('U', cp);
387			card('N', arg);
388			dfname[inchar]++;
389			nact++;
390			continue;
391		}
392		if (sflag)
393			printf("%s: %s: not linked, copying instead\n",
394			    progname, arg);
395
396		if (f) {
397			/*
398			 * The user wants the file removed after it is copied
399			 * to the spool area, so see if the file can be moved
400			 * instead of copy/unlink'ed.  This is much faster and
401			 * uses less spool space than copying the file.  This
402			 * can be very significant when running services like
403			 * samba, pcnfs, CAP, et al.
404			 */
405			PRIV_START
406			didlink = 0;
407			/*
408			 * There are several things to check to avoid any
409			 * security issues.  Some of these are redundant
410			 * under BSD's, but are necessary when lpr is built
411			 * under some other OS's (which I do do...)
412			 */
413			if (lstat(arg, &statb1) < 0)
414				goto nohardlink;
415			if (S_ISLNK(statb1.st_mode))
416				goto nohardlink;
417			if (link(arg, dfname) != 0)
418				goto nohardlink;
419			didlink = 1;
420			/*
421			 * Make sure the user hasn't tried to trick us via
422			 * any race conditions
423			 */
424			if (lstat(dfname, &statb2) < 0)
425				goto nohardlink;
426			if (statb1.st_dev != statb2.st_dev)
427				goto nohardlink;
428			if (statb1.st_ino != statb2.st_ino)
429				goto nohardlink;
430			/*
431			 * Skip if the file already had multiple hard links,
432			 * because changing the owner and access-bits would
433			 * change ALL versions of the file
434			 */
435			if (statb2.st_nlink > 2)
436				goto nohardlink;
437			/*
438			 * If we can access and remove the original file
439			 * without special setuid-ness then this method is
440			 * safe.  Otherwise, abandon the move and fall back
441			 * to the (usual) copy method.
442			 */
443			PRIV_END
444			ret = access(dfname, R_OK);
445			if (ret == 0)
446				ret = unlink(arg);
447			PRIV_START
448			if (ret != 0)
449				goto nohardlink;
450			/*
451			 * Unlink of user file was successful.  Change the
452			 * owner and permissions, add entries to the control
453			 * file, and skip the file copying step.
454			 */
455			chown(dfname, pp->daemon_user, getegid());
456			chmod(dfname, S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP);
457			PRIV_END
458			if (format == 'p')
459				card('T', title ? title : arg);
460			for (i = 0; i < ncopies; i++)
461				card(format, &dfname[inchar-2]);
462			card('U', &dfname[inchar-2]);
463			card('N', arg);
464			nact++;
465			continue;
466		nohardlink:
467			if (didlink)
468				unlink(dfname);
469			PRIV_END           /* restore old uid */
470		} /* end: if (f) */
471
472		if ((i = open(arg, O_RDONLY)) < 0) {
473			printf("%s: cannot open %s\n", progname, arg);
474		} else {
475			copy(pp, i, arg);
476			(void) close(i);
477			if (f && unlink(arg) < 0)
478				printf("%s: %s: not removed\n", progname, arg);
479		}
480	}
481
482	if (nact) {
483		(void) close(tfd);
484		tfname[inchar]--;
485		/*
486		 * Touch the control file to fix position in the queue.
487		 */
488		PRIV_START
489		if ((tfd = open(tfname, O_RDWR)) >= 0) {
490			char touch_c;
491
492			if (read(tfd, &touch_c, 1) == 1 &&
493			    lseek(tfd, (off_t)0, 0) == 0 &&
494			    write(tfd, &touch_c, 1) != 1) {
495				printf("%s: cannot touch %s\n", progname,
496				    tfname);
497				tfname[inchar]++;
498				cleanup(0);
499			}
500			(void) close(tfd);
501		}
502		if (link(tfname, cfname) < 0) {
503			printf("%s: cannot rename %s\n", progname, cfname);
504			tfname[inchar]++;
505			cleanup(0);
506		}
507		unlink(tfname);
508		PRIV_END
509		if (qflag)		/* just q things up */
510			exit(0);
511		if (!startdaemon(pp))
512			printf("jobs queued, but cannot start daemon.\n");
513		exit(0);
514	}
515	cleanup(0);
516	return (1);
517	/* NOTREACHED */
518}
519
520/*
521 * Create the file n and copy from file descriptor f.
522 */
523static void
524copy(const struct printer *pp, int f, const char n[])
525{
526	register int fd, i, nr, nc;
527	char buf[BUFSIZ];
528
529	if (format == 'p')
530		card('T', title ? title : n);
531	for (i = 0; i < ncopies; i++)
532		card(format, &dfname[inchar-2]);
533	card('U', &dfname[inchar-2]);
534	card('N', n);
535	fd = nfile(dfname);
536	nr = nc = 0;
537	while ((i = read(f, buf, BUFSIZ)) > 0) {
538		if (write(fd, buf, i) != i) {
539			printf("%s: %s: temp file write error\n", progname, n);
540			break;
541		}
542		nc += i;
543		if (nc >= BUFSIZ) {
544			nc -= BUFSIZ;
545			nr++;
546			if (pp->max_blocks > 0 && nr > pp->max_blocks) {
547				printf("%s: %s: copy file is too large\n",
548				    progname, n);
549				break;
550			}
551		}
552	}
553	(void) close(fd);
554	if (nc==0 && nr==0)
555		printf("%s: %s: empty input file\n", progname,
556		    f ? n : "stdin");
557	else
558		nact++;
559}
560
561/*
562 * Try and link the file to dfname. Return a pointer to the full
563 * path name if successful.
564 */
565static const char *
566linked(const char *file)
567{
568	register char *cp;
569	static char buf[MAXPATHLEN];
570	register int ret;
571
572	if (*file != '/') {
573		if (getcwd(buf, sizeof(buf)) == NULL)
574			return(NULL);
575		while (file[0] == '.') {
576			switch (file[1]) {
577			case '/':
578				file += 2;
579				continue;
580			case '.':
581				if (file[2] == '/') {
582					if ((cp = strrchr(buf, '/')) != NULL)
583						*cp = '\0';
584					file += 3;
585					continue;
586				}
587			}
588			break;
589		}
590		strncat(buf, "/", sizeof(buf) - strlen(buf) - 1);
591		strncat(buf, file, sizeof(buf) - strlen(buf) - 1);
592		file = buf;
593	}
594	PRIV_START
595	ret = symlink(file, dfname);
596	PRIV_END
597	return(ret ? NULL : file);
598}
599
600/*
601 * Put a line into the control file.
602 */
603static void
604card(int c, const char *p2)
605{
606	char buf[BUFSIZ];
607	register char *p1 = buf;
608	size_t len = 2;
609
610	*p1++ = c;
611	while ((c = *p2++) != '\0' && len < sizeof(buf)) {
612		*p1++ = (c == '\n') ? ' ' : c;
613		len++;
614	}
615	*p1++ = '\n';
616	write(tfd, buf, len);
617}
618
619/*
620 * Create a new file in the spool directory.
621 */
622static int
623nfile(char *n)
624{
625	register int f;
626	int oldumask = umask(0);		/* should block signals */
627
628	PRIV_START
629	f = open(n, O_WRONLY | O_EXCL | O_CREAT, FILMOD);
630	(void) umask(oldumask);
631	if (f < 0) {
632		printf("%s: cannot create %s\n", progname, n);
633		cleanup(0);
634	}
635	if (fchown(f, userid, -1) < 0) {
636		printf("%s: cannot chown %s\n", progname, n);
637		cleanup(0);	/* cleanup does exit */
638	}
639	PRIV_END
640	if (++n[inchar] > 'z') {
641		if (++n[inchar-2] == 't') {
642			printf("too many files - break up the job\n");
643			cleanup(0);
644		}
645		n[inchar] = 'A';
646	} else if (n[inchar] == '[')
647		n[inchar] = 'a';
648	return(f);
649}
650
651/*
652 * Cleanup after interrupts and errors.
653 */
654static void
655cleanup(int signo __unused)
656{
657	register int i;
658
659	signal(SIGHUP, SIG_IGN);
660	signal(SIGINT, SIG_IGN);
661	signal(SIGQUIT, SIG_IGN);
662	signal(SIGTERM, SIG_IGN);
663	i = inchar;
664	PRIV_START
665	if (tfname)
666		do
667			unlink(tfname);
668		while (tfname[i]-- != 'A');
669	if (cfname)
670		do
671			unlink(cfname);
672		while (cfname[i]-- != 'A');
673	if (dfname)
674		do {
675			do
676				unlink(dfname);
677			while (dfname[i]-- != 'A');
678			dfname[i] = 'z';
679		} while (dfname[i-2]-- != 'd');
680	exit(1);
681}
682
683/*
684 * Test to see if this is a printable file.
685 * Return -1 if it is not, 0 if its printable, and 1 if
686 * we should remove it after printing.
687 */
688static int
689test(const char *file)
690{
691	size_t dlen;
692	int fd;
693	char *cp, *dirpath;
694
695	if (access(file, 4) < 0) {
696		printf("%s: cannot access %s\n", progname, file);
697		return(-1);
698	}
699	if (stat(file, &statb) < 0) {
700		printf("%s: cannot stat %s\n", progname, file);
701		return(-1);
702	}
703	if ((statb.st_mode & S_IFMT) == S_IFDIR) {
704		printf("%s: %s is a directory\n", progname, file);
705		return(-1);
706	}
707	if (statb.st_size == 0) {
708		printf("%s: %s is an empty file\n", progname, file);
709		return(-1);
710 	}
711	if ((fd = open(file, O_RDONLY)) < 0) {
712		printf("%s: cannot open %s\n", progname, file);
713		return(-1);
714	}
715	(void) close(fd);
716	if (rflag) {
717		/*
718		 * aside: note that 'cp' is technically a 'const char *'
719		 * (because it points into 'file'), even though strrchr
720		 * returns a value of type 'char *'.
721		 */
722		if ((cp = strrchr(file, '/')) == NULL) {
723			if (checkwriteperm(file,".") == 0)
724				return(1);
725		} else {
726			if (cp == file) {
727				fd = checkwriteperm(file,"/");
728			} else {
729				/* strlcpy will change the '/' to '\0' */
730				dlen = cp - file + 1;
731				dirpath = malloc(dlen);
732				strlcpy(dirpath, file, dlen);
733				fd = checkwriteperm(file, dirpath);
734				free(dirpath);
735			}
736			if (fd == 0)
737				return(1);
738		}
739		printf("%s: %s: is not removable by you\n", progname, file);
740	}
741	return(0);
742}
743
744static int
745checkwriteperm(const char *file, const char *directory)
746{
747	struct	stat	stats;
748	if (access(directory, W_OK) == 0) {
749		stat(directory, &stats);
750		if (stats.st_mode & S_ISVTX) {
751			stat(file, &stats);
752			if(stats.st_uid == userid) {
753				return(0);
754			}
755		} else return(0);
756	}
757	return(-1);
758}
759
760/*
761 * itoa - integer to string conversion
762 */
763static char *
764itoa(int i)
765{
766	static char b[10] = "########";
767	register char *p;
768
769	p = &b[8];
770	do
771		*p-- = i%10 + '0';
772	while (i /= 10);
773	return(++p);
774}
775
776/*
777 * Perform lookup for printer name or abbreviation --
778 */
779static void
780chkprinter(const char *ptrname, struct printer *pp)
781{
782	int status;
783
784	init_printer(pp);
785	status = getprintcap(ptrname, pp);
786	switch(status) {
787	case PCAPERR_OSERR:
788	case PCAPERR_TCLOOP:
789		errx(1, "%s: %s", ptrname, pcaperr(status));
790	case PCAPERR_NOTFOUND:
791		errx(1, "%s: unknown printer", ptrname);
792	case PCAPERR_TCOPEN:
793		warnx("%s: unresolved tc= reference(s)", ptrname);
794	}
795}
796
797/*
798 * Tell the user what we wanna get.
799 */
800static void
801usage(void)
802{
803	fprintf(stderr, "%s\n",
804"usage: lpr [-Pprinter] [-#num] [-C class] [-J job] [-T title] [-U user]\n"
805	"\t[-Z daemon-options] [-i[numcols]] [-i[numcols]] [-1234 font]\n"
806	"\t[-L locale] [-wnum] [-cdfghlnmprstv] [name ...]");
807	exit(1);
808}
809
810
811/*
812 * Make the temp files.
813 */
814static void
815mktemps(const struct printer *pp)
816{
817	register int len, fd, n;
818	register char *cp;
819	char buf[BUFSIZ];
820
821	(void) snprintf(buf, sizeof(buf), "%s/.seq", pp->spool_dir);
822	PRIV_START
823	if ((fd = open(buf, O_RDWR|O_CREAT, 0664)) < 0) {
824		printf("%s: cannot create %s\n", progname, buf);
825		exit(1);
826	}
827	if (flock(fd, LOCK_EX)) {
828		printf("%s: cannot lock %s\n", progname, buf);
829		exit(1);
830	}
831	PRIV_END
832	n = 0;
833	if ((len = read(fd, buf, sizeof(buf))) > 0) {
834		for (cp = buf; len--; ) {
835			if (*cp < '0' || *cp > '9')
836				break;
837			n = n * 10 + (*cp++ - '0');
838		}
839	}
840	len = strlen(pp->spool_dir) + strlen(local_host) + 8;
841	tfname = lmktemp(pp, "tf", n, len);
842	cfname = lmktemp(pp, "cf", n, len);
843	dfname = lmktemp(pp, "df", n, len);
844	inchar = strlen(pp->spool_dir) + 3;
845	n = (n + 1) % 1000;
846	(void) lseek(fd, (off_t)0, 0);
847	snprintf(buf, sizeof(buf), "%03d\n", n);
848	(void) write(fd, buf, strlen(buf));
849	(void) close(fd);	/* unlocks as well */
850}
851
852/*
853 * Make a temp file name.
854 */
855static char *
856lmktemp(const struct printer *pp, const char *id, int num, int len)
857{
858	register char *s;
859
860	if ((s = malloc(len)) == NULL)
861		errx(1, "out of memory");
862	(void) snprintf(s, len, "%s/%sA%03d%s", pp->spool_dir, id, num,
863	    local_host);
864	return(s);
865}
866