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