Sweep-fix comparing pointers with 0 (and assigning 0 to pointers).
[dragonfly.git] / usr.sbin / lpr / lpr / lpr.c
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 }