xref: /dragonfly/usr.sbin/lpr/lpr/lpr.c (revision 479ab7f0)
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. Neither the name of the University nor the names of its contributors
20  *    may be used to endorse or promote products derived from this software
21  *    without specific prior written permission.
22  *
23  * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
24  * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
25  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
26  * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
27  * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
28  * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
29  * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
30  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
31  * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
32  * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
33  * SUCH DAMAGE.
34  *
35  * @(#) Copyright (c) 1983, 1989, 1993 The Regents of the University of California.  All rights reserved.
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 			indent = strtol(optarg, &p, 10);
219 			if (*p)
220 				errx(1, "Bad argument to -i, number expected");
221 			break;
222 
223 		case 'm':		/* send mail when done */
224 			mailflg++;
225 			break;
226 
227 		case 'q':		/* just queue job */
228 			qflag++;
229 			break;
230 
231 		case 'r':		/* remove file when done */
232 			rflag++;
233 			break;
234 
235 		case 's':		/* try to link files */
236 			sflag++;
237 			break;
238 
239 		case 'w':		/* versatec page width */
240 			width = optarg;
241 			break;
242 
243 		case ':':		/* catch "missing argument" error */
244 			if (optopt == 'i') {
245 				iflag++; /* -i without args is valid */
246 				indent = 8;
247 			} else
248 				errs++;
249 			break;
250 
251 		default:
252 			errs++;
253 		}
254 	argc -= optind;
255 	argv += optind;
256 	if (errs)
257 		usage();
258 	if (printer == NULL && (printer = getenv("PRINTER")) == NULL)
259 		printer = DEFLP;
260 	chkprinter(printer, pp);
261 	if (pp->no_copies && ncopies > 1)
262 		errx(1, "multiple copies are not allowed");
263 	if (pp->max_copies > 0 && ncopies > pp->max_copies)
264 		errx(1, "only %ld copies are allowed", pp->max_copies);
265 	/*
266 	 * Get the identity of the person doing the lpr using the same
267 	 * algorithm as lprm.  Actually, not quite -- lprm will override
268 	 * the login name with "root" if the user is running as root;
269 	 * the daemon actually checks for the string "root" in its
270 	 * permission checking.  Sigh.
271 	 */
272 	userid = getuid();
273 	if (Uflag) {
274 		if (userid != 0 && userid != pp->daemon_user)
275 			errx(1, "only privileged users may use the `-U' flag");
276 		lpr_username = Uflag;		/* -U person doing 'lpr' */
277 	} else {
278 		lpr_username = getlogin();	/* person doing 'lpr' */
279 		if (userid != pp->daemon_user || lpr_username == NULL) {
280 			if ((pw = getpwuid(userid)) == NULL)
281 				errx(1, "Who are you?");
282 			lpr_username = pw->pw_name;
283 		}
284 	}
285 
286 	/*
287 	 * Check for restricted group access.
288 	 */
289 	if (pp->restrict_grp != NULL && userid != pp->daemon_user) {
290 		if ((gptr = getgrnam(pp->restrict_grp)) == NULL)
291 			errx(1, "Restricted group specified incorrectly");
292 		if (gptr->gr_gid != getgid()) {
293 			while (*gptr->gr_mem != NULL) {
294 				if ((strcmp(lpr_username, *gptr->gr_mem)) == 0)
295 					break;
296 				gptr->gr_mem++;
297 			}
298 			if (*gptr->gr_mem == NULL)
299 				errx(1, "Not a member of the restricted group");
300 		}
301 	}
302 	/*
303 	 * Check to make sure queuing is enabled if userid is not root.
304 	 */
305 	lock_file_name(pp, buf, sizeof buf);
306 	if (userid && stat(buf, &stb) == 0 && (stb.st_mode & LFM_QUEUE_DIS))
307 		errx(1, "Printer queue is disabled");
308 	/*
309 	 * Initialize the control file.
310 	 */
311 	mktemps(pp);
312 	tfd = nfile(tfname);
313 	seteuid(euid);
314 	fchown(tfd, pp->daemon_user, -1);
315 	/* owned by daemon for protection */
316 	seteuid(uid);
317 	card('H', local_host);
318 	card('P', lpr_username);
319 	card('C', class);
320 	if (hdr && !pp->no_header) {
321 		if (jobname == NULL) {
322 			if (argc == 0)
323 				jobname = "stdin";
324 			else
325 				jobname = ((arg = strrchr(argv[0], '/'))
326 					   ? arg + 1 : argv[0]);
327 		}
328 		card('J', jobname);
329 		card('L', lpr_username);
330 	}
331 	if (format != 'p' && Zflag != NULL)
332 		card('Z', Zflag);
333 	if (iflag)
334 		card('I', itoa(indent));
335 	if (mailflg)
336 		card('M', lpr_username);
337 	if (format == 't' || format == 'n' || format == 'd')
338 		for (i = 0; i < 4; i++)
339 			if (fonts[i] != NULL)
340 				card('1'+i, fonts[i]);
341 	if (width != NULL)
342 		card('W', width);
343 	/*
344 	 * XXX
345 	 * Our use of `Z' here is incompatible with LPRng's
346 	 * use.  We assume that the only use of our existing
347 	 * `Z' card is as shown for `p' format (pr) files.
348 	 */
349 	if (format == 'p') {
350 		char *s;
351 
352 		if (locale)
353 			card('Z', locale);
354 		else if ((s = setlocale(LC_TIME, "")) != NULL)
355 			card('Z', s);
356 	}
357 
358 	/*
359 	 * Read the files and spool them.
360 	 */
361 	if (argc == 0)
362 		copy(pp, 0, " ");
363 	else while (argc--) {
364 		if (argv[0][0] == '-' && argv[0][1] == '\0') {
365 			/* use stdin */
366 			copy(pp, 0, " ");
367 			argv++;
368 			continue;
369 		}
370 		if ((f = test(arg = *argv++)) < 0)
371 			continue;	/* file unreasonable */
372 
373 		if (sflag && (cp = linked(arg)) != NULL) {
374 			snprintf(buf, sizeof(buf), "%d %"PRId64, statb.st_dev,
375 				 (uint64_t)statb.st_ino);
376 			card('S', buf);
377 			if (format == 'p')
378 				card('T', title ? title : arg);
379 			for (i = 0; i < ncopies; i++)
380 				card(format, &dfname[inchar-2]);
381 			card('U', &dfname[inchar-2]);
382 			if (f)
383 				card('U', cp);
384 			card('N', arg);
385 			dfname[inchar]++;
386 			nact++;
387 			continue;
388 		}
389 		if (sflag)
390 			printf("%s: %s: not linked, copying instead\n",
391 			    progname, arg);
392 
393 		if (f) {
394 			/*
395 			 * The user wants the file removed after it is copied
396 			 * to the spool area, so see if the file can be moved
397 			 * instead of copy/unlink'ed.  This is much faster and
398 			 * uses less spool space than copying the file.  This
399 			 * can be very significant when running services like
400 			 * samba, pcnfs, CAP, et al.
401 			 */
402 			seteuid(euid);
403 			didlink = 0;
404 			/*
405 			 * There are several things to check to avoid any
406 			 * security issues.  Some of these are redundant
407 			 * under BSD's, but are necessary when lpr is built
408 			 * under some other OS's (which I do do...)
409 			 */
410 			if (lstat(arg, &statb1) < 0)
411 				goto nohardlink;
412 			if (S_ISLNK(statb1.st_mode))
413 				goto nohardlink;
414 			if (link(arg, dfname) != 0)
415 				goto nohardlink;
416 			didlink = 1;
417 			/*
418 			 * Make sure the user hasn't tried to trick us via
419 			 * any race conditions
420 			 */
421 			if (lstat(dfname, &statb2) < 0)
422 				goto nohardlink;
423 			if (statb1.st_dev != statb2.st_dev)
424 				goto nohardlink;
425 			if (statb1.st_ino != statb2.st_ino)
426 				goto nohardlink;
427 			/*
428 			 * Skip if the file already had multiple hard links,
429 			 * because changing the owner and access-bits would
430 			 * change ALL versions of the file
431 			 */
432 			if (statb2.st_nlink > 2)
433 				goto nohardlink;
434 			/*
435 			 * If we can access and remove the original file
436 			 * without special setuid-ness then this method is
437 			 * safe.  Otherwise, abandon the move and fall back
438 			 * to the (usual) copy method.
439 			 */
440 			seteuid(uid);
441 			ret = access(dfname, R_OK);
442 			if (ret == 0)
443 				ret = unlink(arg);
444 			seteuid(euid);
445 			if (ret != 0)
446 				goto nohardlink;
447 			/*
448 			 * Unlink of user file was successful.  Change the
449 			 * owner and permissions, add entries to the control
450 			 * file, and skip the file copying step.
451 			 */
452 			chown(dfname, pp->daemon_user, getegid());
453 			chmod(dfname, S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP);
454 			seteuid(uid);
455 			if (format == 'p')
456 				card('T', title ? title : arg);
457 			for (i = 0; i < ncopies; i++)
458 				card(format, &dfname[inchar-2]);
459 			card('U', &dfname[inchar-2]);
460 			card('N', arg);
461 			nact++;
462 			continue;
463 		nohardlink:
464 			if (didlink)
465 				unlink(dfname);
466 			seteuid(uid);           /* restore old uid */
467 		} /* end: if (f) */
468 
469 		if ((i = open(arg, O_RDONLY)) < 0) {
470 			printf("%s: cannot open %s\n", progname, arg);
471 		} else {
472 			copy(pp, i, arg);
473 			close(i);
474 			if (f && unlink(arg) < 0)
475 				printf("%s: %s: not removed\n", progname, arg);
476 		}
477 	}
478 
479 	if (nact) {
480 		close(tfd);
481 		tfname[inchar]--;
482 		/*
483 		 * Touch the control file to fix position in the queue.
484 		 */
485 		seteuid(euid);
486 		if ((tfd = open(tfname, O_RDWR)) >= 0) {
487 			char touch_c;
488 
489 			if (read(tfd, &touch_c, 1) == 1 &&
490 			    lseek(tfd, (off_t)0, 0) == 0 &&
491 			    write(tfd, &touch_c, 1) != 1) {
492 				printf("%s: cannot touch %s\n", progname,
493 				    tfname);
494 				tfname[inchar]++;
495 				cleanup(0);
496 			}
497 			close(tfd);
498 		}
499 		if (link(tfname, cfname) < 0) {
500 			printf("%s: cannot rename %s\n", progname, cfname);
501 			tfname[inchar]++;
502 			cleanup(0);
503 		}
504 		unlink(tfname);
505 		seteuid(uid);
506 		if (qflag)		/* just q things up */
507 			exit(0);
508 		if (!startdaemon(pp))
509 			printf("jobs queued, but cannot start daemon.\n");
510 		exit(0);
511 	}
512 	cleanup(0);
513 	return (1);
514 	/* NOTREACHED */
515 }
516 
517 /*
518  * Create the file n and copy from file descriptor f.
519  */
520 static void
521 copy(const struct printer *pp, int f, const char n[])
522 {
523 	int fd, i, nr, nc;
524 	char buf[BUFSIZ];
525 
526 	if (format == 'p')
527 		card('T', title ? title : n);
528 	for (i = 0; i < ncopies; i++)
529 		card(format, &dfname[inchar-2]);
530 	card('U', &dfname[inchar-2]);
531 	card('N', n);
532 	fd = nfile(dfname);
533 	nr = nc = 0;
534 	while ((i = read(f, buf, BUFSIZ)) > 0) {
535 		if (write(fd, buf, i) != i) {
536 			printf("%s: %s: temp file write error\n", progname, n);
537 			break;
538 		}
539 		nc += i;
540 		if (nc >= BUFSIZ) {
541 			nc -= BUFSIZ;
542 			nr++;
543 			if (pp->max_blocks > 0 && nr > pp->max_blocks) {
544 				printf("%s: %s: copy file is too large\n",
545 				    progname, n);
546 				break;
547 			}
548 		}
549 	}
550 	close(fd);
551 	if (nc==0 && nr==0)
552 		printf("%s: %s: empty input file\n", progname,
553 		    f ? n : "stdin");
554 	else
555 		nact++;
556 }
557 
558 /*
559  * Try and link the file to dfname. Return a pointer to the full
560  * path name if successful.
561  */
562 static const char *
563 linked(const char *file)
564 {
565 	char *cp;
566 	static char buf[MAXPATHLEN];
567 	int ret;
568 
569 	if (*file != '/') {
570 		if (getcwd(buf, sizeof(buf)) == NULL)
571 			return(NULL);
572 		while (file[0] == '.') {
573 			switch (file[1]) {
574 			case '/':
575 				file += 2;
576 				continue;
577 			case '.':
578 				if (file[2] == '/') {
579 					if ((cp = strrchr(buf, '/')) != NULL)
580 						*cp = '\0';
581 					file += 3;
582 					continue;
583 				}
584 			}
585 			break;
586 		}
587 		strncat(buf, "/", sizeof(buf) - strlen(buf) - 1);
588 		strncat(buf, file, sizeof(buf) - strlen(buf) - 1);
589 		file = buf;
590 	}
591 	seteuid(euid);
592 	ret = symlink(file, dfname);
593 	seteuid(uid);
594 	return(ret ? NULL : file);
595 }
596 
597 /*
598  * Put a line into the control file.
599  */
600 static void
601 card(int c, const char *p2)
602 {
603 	char buf[BUFSIZ];
604 	char *p1 = buf;
605 	size_t len = 2;
606 
607 	*p1++ = c;
608 	while ((c = *p2++) != '\0' && len < sizeof(buf)) {
609 		*p1++ = (c == '\n') ? ' ' : c;
610 		len++;
611 	}
612 	*p1++ = '\n';
613 	write(tfd, buf, len);
614 }
615 
616 /*
617  * Create a new file in the spool directory.
618  */
619 static int
620 nfile(char *n)
621 {
622 	int f;
623 	int oldumask = umask(0);		/* should block signals */
624 
625 	seteuid(euid);
626 	f = open(n, O_WRONLY | O_EXCL | O_CREAT, FILMOD);
627 	umask(oldumask);
628 	if (f < 0) {
629 		printf("%s: cannot create %s\n", progname, n);
630 		cleanup(0);
631 	}
632 	if (fchown(f, userid, -1) < 0) {
633 		printf("%s: cannot chown %s\n", progname, n);
634 		cleanup(0);	/* cleanup does exit */
635 	}
636 	seteuid(uid);
637 	if (++n[inchar] > 'z') {
638 		if (++n[inchar-2] == 't') {
639 			printf("too many files - break up the job\n");
640 			cleanup(0);
641 		}
642 		n[inchar] = 'A';
643 	} else if (n[inchar] == '[')
644 		n[inchar] = 'a';
645 	return(f);
646 }
647 
648 /*
649  * Cleanup after interrupts and errors.
650  */
651 static void
652 cleanup(int signo __unused)
653 {
654 	int i;
655 
656 	signal(SIGHUP, SIG_IGN);
657 	signal(SIGINT, SIG_IGN);
658 	signal(SIGQUIT, SIG_IGN);
659 	signal(SIGTERM, SIG_IGN);
660 	i = inchar;
661 	seteuid(euid);
662 	if (tfname)
663 		do
664 			unlink(tfname);
665 		while (tfname[i]-- != 'A');
666 	if (cfname)
667 		do
668 			unlink(cfname);
669 		while (cfname[i]-- != 'A');
670 	if (dfname)
671 		do {
672 			do
673 				unlink(dfname);
674 			while (dfname[i]-- != 'A');
675 			dfname[i] = 'z';
676 		} while (dfname[i-2]-- != 'd');
677 	exit(1);
678 }
679 
680 /*
681  * Test to see if this is a printable file.
682  * Return -1 if it is not, 0 if its printable, and 1 if
683  * we should remove it after printing.
684  */
685 static int
686 test(const char *file)
687 {
688 	struct exec execb;
689 	size_t dlen;
690 	int fd;
691 	char *cp, *dirpath;
692 
693 	if (access(file, 4) < 0) {
694 		printf("%s: cannot access %s\n", progname, file);
695 		return(-1);
696 	}
697 	if (stat(file, &statb) < 0) {
698 		printf("%s: cannot stat %s\n", progname, file);
699 		return(-1);
700 	}
701 	if ((statb.st_mode & S_IFMT) == S_IFDIR) {
702 		printf("%s: %s is a directory\n", progname, file);
703 		return(-1);
704 	}
705 	if (statb.st_size == 0) {
706 		printf("%s: %s is an empty file\n", progname, file);
707 		return(-1);
708  	}
709 	if ((fd = open(file, O_RDONLY)) < 0) {
710 		printf("%s: cannot open %s\n", progname, file);
711 		return(-1);
712 	}
713 	/*
714 	 * XXX Shall we add a similar test for ELF?
715 	 */
716 	if (read(fd, &execb, sizeof(execb)) == sizeof(execb) &&
717 	    !N_BADMAG(execb)) {
718 		printf("%s: %s is an executable program", progname, file);
719 		goto error1;
720 	}
721 	close(fd);
722 	if (rflag) {
723 		/*
724 		 * aside: note that 'cp' is technically a 'const char *'
725 		 * (because it points into 'file'), even though strrchr
726 		 * returns a value of type 'char *'.
727 		 */
728 		if ((cp = strrchr(file, '/')) == NULL) {
729 			if (checkwriteperm(file,".") == 0)
730 				return(1);
731 		} else {
732 			if (cp == file) {
733 				fd = checkwriteperm(file,"/");
734 			} else {
735 				/* strlcpy will change the '/' to '\0' */
736 				dlen = cp - file + 1;
737 				dirpath = malloc(dlen);
738 				strlcpy(dirpath, file, dlen);
739 				fd = checkwriteperm(file, dirpath);
740 				free(dirpath);
741 			}
742 			if (fd == 0)
743 				return(1);
744 		}
745 		printf("%s: %s: is not removable by you\n", progname, file);
746 	}
747 	return(0);
748 
749 error1:
750 	printf(" and is unprintable\n");
751 	close(fd);
752 	return(-1);
753 }
754 
755 static int
756 checkwriteperm(const char *file, const char *directory)
757 {
758 	struct	stat	stats;
759 	if (access(directory, W_OK) == 0) {
760 		stat(directory, &stats);
761 		if (stats.st_mode & S_ISVTX) {
762 			stat(file, &stats);
763 			if(stats.st_uid == userid) {
764 				return(0);
765 			}
766 		} else return(0);
767 	}
768 	return(-1);
769 }
770 
771 /*
772  * itoa - integer to string conversion
773  */
774 static char *
775 itoa(int i)
776 {
777 	static char b[10] = "########";
778 	char *p;
779 
780 	p = &b[8];
781 	do
782 		*p-- = i%10 + '0';
783 	while (i /= 10);
784 	return(++p);
785 }
786 
787 /*
788  * Perform lookup for printer name or abbreviation --
789  */
790 static void
791 chkprinter(const char *ptrname, struct printer *pp)
792 {
793 	int status;
794 
795 	init_printer(pp);
796 	status = getprintcap(ptrname, pp);
797 	switch(status) {
798 	case PCAPERR_OSERR:
799 	case PCAPERR_TCLOOP:
800 		errx(1, "%s: %s", ptrname, pcaperr(status));
801 	case PCAPERR_NOTFOUND:
802 		errx(1, "%s: unknown printer", ptrname);
803 	case PCAPERR_TCOPEN:
804 		warnx("%s: unresolved tc= reference(s)", ptrname);
805 	}
806 }
807 
808 /*
809  * Tell the user what we wanna get.
810  */
811 static void
812 usage(void)
813 {
814 	fprintf(stderr, "%s\n",
815 "usage: lpr [-Pprinter] [-#num] [-C class] [-J job] [-T title] [-U user]\n"
816 	"\t[-Z daemon-options] [-i[numcols]] [-i[numcols]] [-1234 font]\n"
817 	"\t[-L locale] [-wnum] [-cdfghlnmprstv] [name ...]");
818 	exit(1);
819 }
820 
821 
822 /*
823  * Make the temp files.
824  */
825 static void
826 mktemps(const struct printer *pp)
827 {
828 	int len, fd, n;
829 	char *cp;
830 	char buf[BUFSIZ];
831 
832 	snprintf(buf, sizeof(buf), "%s/.seq", pp->spool_dir);
833 	seteuid(euid);
834 	if ((fd = open(buf, O_RDWR|O_CREAT, 0661)) < 0) {
835 		printf("%s: cannot create %s\n", progname, buf);
836 		exit(1);
837 	}
838 	if (flock(fd, LOCK_EX)) {
839 		printf("%s: cannot lock %s\n", progname, buf);
840 		exit(1);
841 	}
842 	seteuid(uid);
843 	n = 0;
844 	if ((len = read(fd, buf, sizeof(buf))) > 0) {
845 		for (cp = buf; len--; ) {
846 			if (*cp < '0' || *cp > '9')
847 				break;
848 			n = n * 10 + (*cp++ - '0');
849 		}
850 	}
851 	len = strlen(pp->spool_dir) + strlen(local_host) + 8;
852 	tfname = lmktemp(pp, "tf", n, len);
853 	cfname = lmktemp(pp, "cf", n, len);
854 	dfname = lmktemp(pp, "df", n, len);
855 	inchar = strlen(pp->spool_dir) + 3;
856 	n = (n + 1) % 1000;
857 	lseek(fd, (off_t)0, 0);
858 	snprintf(buf, sizeof(buf), "%03d\n", n);
859 	write(fd, buf, strlen(buf));
860 	close(fd);	/* unlocks as well */
861 }
862 
863 /*
864  * Make a temp file name.
865  */
866 static char *
867 lmktemp(const struct printer *pp, const char *id, int num, int len)
868 {
869 	char *s;
870 
871 	if ((s = malloc(len)) == NULL)
872 		errx(1, "out of memory");
873 	snprintf(s, len, "%s/%sA%03d%s", pp->spool_dir, id, num, local_host);
874 	return(s);
875 }
876