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