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