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