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