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