root/usr.sbin/lpr/lpr/lpr.c
/*      $NetBSD: lpr.c,v 1.47 2023/06/24 05:15:42 msaitoh Exp $ */

/*
 * Copyright (c) 1983, 1989, 1993
 *      The Regents of the University of California.  All rights reserved.
 * (c) UNIX System Laboratories, Inc.
 * All or some portions of this file are derived from material licensed
 * to the University of California by American Telephone and Telegraph
 * Co. or Unix System Laboratories, Inc. and are reproduced herein with
 * the permission of UNIX System Laboratories, Inc.
 *
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 * 3. Neither the name of the University nor the names of its contributors
 *    may be used to endorse or promote products derived from this software
 *    without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
 * SUCH DAMAGE.
 */

#include <sys/cdefs.h>
#ifndef lint
__COPYRIGHT("@(#) Copyright (c) 1983, 1989, 1993\
 The Regents of the University of California.  All rights reserved.");
#if 0
static char sccsid[] = "@(#)lpr.c       8.4 (Berkeley) 4/28/95";
#else
__RCSID("$NetBSD: lpr.c,v 1.47 2023/06/24 05:15:42 msaitoh Exp $");
#endif
#endif /* not lint */

/*
 *      lpr -- off line print
 *
 * Allows multiple printers and printers on remote machines by
 * using information from a printer data base.
 */

#include <sys/param.h>
#include <sys/stat.h>
#include <sys/file.h>

#include <dirent.h>
#include <fcntl.h>
#include <signal.h>
#include <syslog.h>
#include <pwd.h>
#include <grp.h>
#include <unistd.h>
#include <stdlib.h>
#include <stdio.h>
#include <ctype.h>
#include <string.h>
#include <errno.h>
#include <err.h>

#include "lp.h"
#include "lp.local.h"
#include "pathnames.h"

static char     *cfname;        /* daemon control files, linked from tf's */
static char     *class = host;  /* class title on header page */
static char     *dfname;        /* data files */
static char     *fonts[4];      /* troff font names */
static char      format = 'f';  /* format char for printing files */
static int       hdr = 1;       /* print header or not (default is yes) */
static int       iflag;         /* indentation wanted */
static int       inchar;        /* location to increment char in file names */
static int       indent;        /* amount to indent */
static const char *jobname;     /* job name on header page */
static int       mailflg;       /* send mail */
static int       nact;          /* number of jobs to act on */
static int       ncopies = 1;   /* # of copies to make */
static const char *person;      /* user name */
static int       qflag;         /* q job, but don't exec daemon */
static int       reqid;         /* request id */
static int       rflag;         /* remove files upon completion */      
static int       Rflag;         /* print request id - like POSIX lp */
static int       sflag;         /* symbolic link flag */
static int       tfd;           /* control file descriptor */
static char     *tfname;        /* tmp copy of cf before linking */
static char     *title;         /* pr'ing title */
static int       userid;        /* user id */
static char     *width;         /* width for versatec printing */

static struct stat statb;

static void      card(int, const char *);
static void      chkprinter(const char *);
static void      cleanup(int) __dead;
static void      copy(int, const char *);
static char     *itoa(int);
static const char       *linked(const char *);
static char     *lmktemp(const char *, int, int);
static void      mktemps(void);
static int       nfile(char *);
static int       test(const char *);
static void      usage(void) __dead;

uid_t   uid, euid;

int
main(int argc, char *argv[])
{
        struct passwd *pw;
        struct group *gptr;
        char *arg;
        const char *cp;
        char buf[MAXPATHLEN];
        int i, f, errs, c;
        struct stat stb;
        int oerrno;

        euid = geteuid();
        uid = getuid();
        seteuid(uid);

        if (signal(SIGHUP, SIG_IGN) != SIG_IGN)
                signal(SIGHUP, cleanup);
        if (signal(SIGINT, SIG_IGN) != SIG_IGN)
                signal(SIGINT, cleanup);
        if (signal(SIGQUIT, SIG_IGN) != SIG_IGN)
                signal(SIGQUIT, cleanup);
        if (signal(SIGTERM, SIG_IGN) != SIG_IGN)
                signal(SIGTERM, cleanup);

        setprogname(*argv);
        gethostname(host, sizeof (host));
        host[sizeof(host) - 1] = '\0';
        openlog("lpd", 0, LOG_LPR);

        errs = 0;
        while ((c = getopt(argc, argv,
            ":#:1:2:3:4:C:J:P:RT:U:cdfghi:lmnopqrstvw:")) != -1) {
                switch (c) {

                case '#':               /* n copies */
                        if (isdigit((unsigned char)*optarg)) {
                                i = atoi(optarg);
                                if (i > 0)
                                        ncopies = i;
                        }
                        break;

                case '4':               /* troff fonts */
                case '3':
                case '2':
                case '1':
                        fonts[optopt - '1'] = optarg;
                        break;

                case 'C':               /* classification spec */
                        hdr++;
                        class = optarg;
                        break;

                case 'J':               /* job name */
                        hdr++;
                        jobname = optarg;
                        break;

                case 'P':               /* specify printer name */
                        printer = optarg;
                        break;

                case 'R':               /* print request id */
                        Rflag++;
                        break;
                        
                case 'T':               /* pr's title line */
                        title = optarg;
                        break;

                case 'U':               /* user name */
                        hdr++;
                        person = optarg;
                        break;

                case 'c':               /* print cifplot output */
                case 'd':               /* print tex output (dvi files) */
                case 'g':               /* print graph(1G) output */
                case 'l':               /* literal output */
                case 'o':               /* print postscript output */
                case 'n':               /* print ditroff output */
                case 'p':               /* print using ``pr'' */
                case 't':               /* print troff output (cat files) */
                case 'v':               /* print vplot output */
                        format = optopt;
                        break;

                case 'f':               /* print fortran output */
                        format = 'r';
                        break;

                case 'h':               /* toggle want of header page */
                        hdr = !hdr;
                        break;

                case 'i':               /* indent output */
                        iflag++;
                        indent = atoi(optarg);
                        if (indent < 0)
                                indent = 8;
                        break;

                case 'm':               /* send mail when done */
                        mailflg++;
                        break;

                case 'q':               /* just q job */
                        qflag++;
                        break;

                case 'r':               /* remove file when done */
                        rflag++;
                        break;

                case 's':               /* try to link files */
                        sflag++;
                        break;

                case 'w':               /* versatec page width */
                        width = optarg;
                        break;

                case ':':               /* catch "missing argument" error */
                        if (optopt == 'i') {
                                iflag++; /* -i without args is valid */
                                indent = 8;
                        } else
                                errs++;
                        break;

                default:
                        errs++;
                }
        }
        argc -= optind;
        argv += optind;
        if (errs)
                usage();
        if (printer == NULL && (printer = getenv("PRINTER")) == NULL)
                printer = DEFLP;
        chkprinter(printer);
        if (SC && ncopies > 1)
                errx(EXIT_FAILURE, "multiple copies are not allowed");
        if (MC > 0 && ncopies > MC)
                errx(EXIT_FAILURE, "only %ld copies are allowed", MC);
        /*
         * Get the identity of the person doing the lpr using the same
         * algorithm as lprm. 
         */
        userid = getuid();
        if (userid != DU || person == 0) {
                if ((pw = getpwuid(userid)) == NULL)
                        errx(EXIT_FAILURE, "Who are you?");
                person = pw->pw_name;
        }
        /*
         * Check for restricted group access.
         */
        if (RG != NULL && userid != DU) {
                if ((gptr = getgrnam(RG)) == NULL)
                        errx(EXIT_FAILURE,
                             "Restricted group specified incorrectly");
                if (gptr->gr_gid != getgid()) {
                        while (*gptr->gr_mem != NULL) {
                                if ((strcmp(person, *gptr->gr_mem)) == 0)
                                        break;
                                gptr->gr_mem++;
                        }
                        if (*gptr->gr_mem == NULL)
                                errx(EXIT_FAILURE,
                                     "Not a member of the restricted group");
                }
        }
        /*
         * Check to make sure queuing is enabled if userid is not root.
         */
        (void)snprintf(buf, sizeof buf, "%s/%s", SD, LO);
        if (userid && stat(buf, &stb) == 0 && (stb.st_mode & S_IXGRP))
                errx(EXIT_FAILURE, "Printer queue is disabled");
        /*
         * Initialize the control file.
         */
        mktemps();
        tfd = nfile(tfname);
        seteuid(euid);
        (void)fchown(tfd, DU, -1);      /* owned by daemon for protection */
        seteuid(uid);
        card('H', host);
        card('P', person);
        if (hdr && !SH) {
                if (jobname == NULL) {
                        if (argc == 0)
                                jobname = "stdin";
                        else
                                jobname = (arg = strrchr(argv[0], '/')) ?
                                    arg+1 : argv[0];
                }
                card('J', jobname);
                card('C', class);
                card('L', person);
        }
        if (iflag)
                card('I', itoa(indent));
        if (mailflg)
                card('M', person);
        if (format == 't' || format == 'n' || format == 'd')
                for (i = 0; i < 4; i++)
                        if (fonts[i] != NULL)
                                card('1'+i, fonts[i]);
        if (width != NULL)
                card('W', width);

        /*
         * Read the files and spool them.
         */
        if (argc == 0)
                copy(0, " ");
        else while (argc--) {
                if (argv[0][0] == '-' && argv[0][1] == '\0') {
                        /* use stdin */
                        copy(0, " ");
                        argv++;
                        continue;
                }
                if ((f = test(arg = *argv++)) < 0)
                        continue;       /* file unreasonable */

                if (sflag && (cp = linked(arg)) != NULL) {
                        (void)snprintf(buf, sizeof buf,
                            "%llu %llu",
                            (unsigned long long)statb.st_dev,
                            (unsigned long long)statb.st_ino);
                        card('S', buf);
                        if (format == 'p')
                                card('T', title ? title : arg);
                        for (i = 0; i < ncopies; i++)
                                card(format, &dfname[inchar-2]);
                        card('U', &dfname[inchar-2]);
                        if (f)
                                card('U', cp);
                        card('N', arg);
                        dfname[inchar]++;
                        nact++;
                        continue;
                }
                if (sflag)
                        warnx("%s: not linked, copying instead", arg);
                seteuid(uid);
                if ((i = open(arg, O_RDONLY)) < 0) {
                        oerrno = errno;
                        seteuid(uid);
                        errno = oerrno;
                        warn("cannot open %s", arg);
                        continue;
                } else {
                        copy(i, arg);
                        (void)close(i);
                        if (f && unlink(arg) < 0)
                                warn("%s: not removed", arg);
                }
                seteuid(uid);
        }

        if (nact) {
                (void)close(tfd);
                tfname[inchar]--;
                /*
                 * Touch the control file to fix position in the queue.
                 */
                seteuid(euid);
                if ((tfd = open(tfname, O_RDWR)) >= 0) {
                        char ch;

                        if (read(tfd, &ch, 1) == 1 &&
                            lseek(tfd, (off_t)0, 0) == 0 &&
                            write(tfd, &ch, 1) != 1) {
                                warn("cannot touch %s", tfname);
                                tfname[inchar]++;
                                cleanup(0);
                        }
                        (void)close(tfd);
                }
                if (link(tfname, cfname) < 0) {
                        warn("cannot rename %s", cfname);
                        tfname[inchar]++;
                        cleanup(0);
                }
                unlink(tfname);
                seteuid(uid);
                if (Rflag)
                        printf("request id is %d\n", reqid);
                if (qflag)              /* just queue things up */
                        exit(0);
                if (!startdaemon(printer))
                        printf("jobs queued, but cannot start daemon.\n");
                exit(0);
        }
        cleanup(0);
#ifdef __GNUC__
        return (0);
#endif
        /* NOTREACHED */
}

/*
 * Create the file n and copy from file descriptor f.
 */
static void
copy(int f, const char *n)
{
        int fd, i, nr;
        size_t nc;
        char buf[BUFSIZ];

        if (format == 'p')
                card('T', title ? title : n);
        for (i = 0; i < ncopies; i++)
                card(format, &dfname[inchar-2]);
        card('U', &dfname[inchar-2]);
        card('N', n);
        fd = nfile(dfname);
        nr = nc = 0;
        while ((i = read(f, buf, sizeof buf)) > 0) {
                if (write(fd, buf, i) != i) {
                        warn("%s: temp file write error", n);
                        break;
                }
                nc += i;
                if (nc >= sizeof buf) {
                        nc -= sizeof buf;
                        nr++;
                        if (MX > 0 && nr > MX) {
                                warnx("%s: copy file is too large "
                                    "(check :mx:?)\n", n);
                                break;
                        }
                }
        }
        (void)close(fd);
        if (nc == 0 && nr == 0) 
                printf("%s: %s: empty input file\n", getprogname(),
                    f ? n : "stdin");
        else
                nact++;
}

/*
 * Try and link the file to dfname. Return a pointer to the full
 * path name if successful.
 */
static const char *
linked(const char *file)
{
        char *cp;
        static char buf[BUFSIZ];
        int ret;

        if (*file != '/') {
                /* XXX: 2 and file for "/file" */
                if (getcwd(buf, sizeof(buf) - 2 - strlen(file)) == NULL)
                        return(NULL);
                while (file[0] == '.') {
                        switch (file[1]) {
                        case '/':
                                file += 2;
                                continue;
                        case '.':
                                if (file[2] == '/') {
                                        if ((cp = strrchr(buf, '/')) != NULL)
                                                *cp = '\0';
                                        file += 3;
                                        continue;
                                }
                        }
                        break;
                }
                strlcat(buf, "/", sizeof(buf));
                strlcat(buf, file, sizeof(buf));
                file = buf;
        }
        seteuid(euid);
        ret = symlink(file, dfname);
        seteuid(uid);
        return(ret ? NULL : file);
}

/*
 * Put a line into the control file.
 */
static void
card(int c, const char *p2)
{
        char buf[BUFSIZ];
        char *p1 = buf;
        size_t len = 2;

        if (strlen(p2) > BUFSIZ - 2)
                errx(EXIT_FAILURE,
                     "Internal error:  String longer than %d", BUFSIZ);

        *p1++ = c;
        while ((c = *p2++) != '\0') {
                *p1++ = (c == '\n') ? ' ' : c;
                len++;
        }
        *p1++ = '\n';
        if (write(tfd, buf, len) != (ssize_t)len)
                warn("Control file write error");
}

/*
 * Create a new file in the spool directory.
 */
static int
nfile(char *n)
{
        int f;
        int oldumask = umask(0);                /* should block signals */
        int oerrno;

        seteuid(euid);
        f = open(n, O_WRONLY | O_EXCL | O_CREAT, FILMOD);
        oerrno = errno;
        (void)umask(oldumask);
        if (f < 0) {
                errno = oerrno;
                warn("cannot create %s", n);
                cleanup(0);
        }
        if (fchown(f, userid, -1) < 0) {
                warn("cannot chown %s", n);
                cleanup(0);     /* cleanup does exit */
        }
        seteuid(uid);
        if (++n[inchar] > 'z') {
                if (++n[inchar-2] == 't') {
                        printf("too many files - break up the job\n");
                        cleanup(0);
                }
                n[inchar] = 'A';
        } else if (n[inchar] == '[')
                n[inchar] = 'a';
        return(f);
}

/*
 * Cleanup after interrupts and errors.
 */
static void
cleanup(int signo)
{
        int i;

        signal(SIGHUP, SIG_IGN);
        signal(SIGINT, SIG_IGN);
        signal(SIGQUIT, SIG_IGN);
        signal(SIGTERM, SIG_IGN);
        i = inchar;
        seteuid(euid);
        if (tfname)
                do
                        unlink(tfname);
                while (tfname[i]-- != 'A');
        if (cfname)
                do
                        unlink(cfname);
                while (cfname[i]-- != 'A');
        if (dfname)
                do {
                        do
                                unlink(dfname);
                        while (dfname[i]-- != 'A');
                        dfname[i] = 'z';
                } while (dfname[i-2]-- != 'd');
        exit(1);
}

/*
 * Test to see if this is a printable file.
 * Return -1 if it is not, 0 if its printable, and 1 if
 * we should remove it after printing.
 */
static int
test(const char *file)
{
        int fd;
        char *cp;

        seteuid(uid);
        if (access(file, 4) < 0) {
                warn("cannot access %s", file);
                goto bad;
        }
        if (stat(file, &statb) < 0) {
                warn("cannot stat %s", file);
                goto bad;
        }
        if (S_ISDIR(statb.st_mode)) {
                warnx("%s is a directory", file);
                goto bad;
        }
        if (statb.st_size == 0) {
                warnx("%s is an empty file", file);
                goto bad;
        }
        if ((fd = open(file, O_RDONLY)) < 0) {
                warn("cannot open %s", file);
                goto bad;
        }
        (void)close(fd);
        if (rflag) {
                if ((cp = strrchr(file, '/')) == NULL) {
                        if (access(".", 2) == 0)
                                return(1);
                } else {
                        if (cp == file) {
                                fd = access("/", 2);
                        } else {
                                *cp = '\0';
                                fd = access(file, 2);
                                *cp = '/';
                        }
                        if (fd == 0)
                                return(1);
                }
                warnx("%s: is not removable by you", file);
        }
        return(0);
bad:
        seteuid(uid);
        return(-1);
}

/*
 * itoa - integer to string conversion
 */
static char *
itoa(int i)
{
        static char b[10] = "########";
        char *p;

        p = &b[8];
        do
                *p-- = i%10 + '0';
        while (i /= 10);
        return(++p);
}

/*
 * Perform lookup for printer name or abbreviation --
 */
static void
chkprinter(const char *s)
{
        char *cp;

        getprintcap(s);
        RG = cgetstr(bp, "rg", &cp) == -1 ? NULL : cp;
        if (cgetnum(bp, "mx", &MX) < 0)
                MX = DEFMX;
        if (cgetnum(bp,"mc", &MC) < 0)
                MC = DEFMAXCOPIES;
        if (cgetnum(bp, "du", &DU) < 0)
                DU = DEFUID;
        SC = (cgetcap(bp, "sc", ':') != NULL);
}

/*
 * Make the temp files.
 */
static void
mktemps(void)
{
        int len, fd, n;
        char *cp;
        char buf[MAXPATHLEN];

        (void)snprintf(buf, sizeof(buf), "%s/.seq", SD);
        seteuid(euid);
        if ((fd = open(buf, O_RDWR|O_CREAT, 0664)) < 0)
                err(1, "cannot create %s", buf);
        if (flock(fd, LOCK_EX))
                err(1, "cannot lock %s", buf);
        seteuid(uid);
        n = 0;
        if ((len = read(fd, buf, sizeof(buf))) > 0) {
                for (cp = buf; len--; ) {
                        if (*cp < '0' || *cp > '9')
                                break;
                        n = n * 10 + (*cp++ - '0');
                }
        }
        reqid = n;
        len = strlen(SD) + strlen(host) + 8;
        tfname = lmktemp("tf", n, len);
        cfname = lmktemp("cf", n, len);
        dfname = lmktemp("df", n, len);
        inchar = strlen(SD) + 3;
        n = (n + 1) % 1000;
        (void)lseek(fd, (off_t)0, 0);
        (void)snprintf(buf, sizeof(buf), "%03d\n", n);
        (void)write(fd, buf, strlen(buf));
        (void)close(fd);        /* unlocks as well */
}

/*
 * Make a temp file name.
 */
static char *
lmktemp(const char *id, int num, int len)
{
        char *s;

        if ((s = malloc(len)) == NULL)
                err(EXIT_FAILURE, NULL);
        (void)snprintf(s, len, "%s/%sA%03d%s", SD, id, num, host);
        return(s);
}

static void
usage(void)
{

        fprintf(stderr, 
            "Usage: %s [-Pprinter] [-#num] [-C class] [-J job] [-T title] "
            "[-U user]\n"
            "%s [-i[numcols]] [-1234 font] [-wnum] [-cdfghlmnopqRrstv] "
            "[name ...]\n", getprogname(), getprogname());
        exit(1);
}