aboutsummaryrefslogtreecommitdiff
path: root/usr.sbin/lpr
diff options
context:
space:
mode:
Diffstat (limited to 'usr.sbin/lpr')
-rw-r--r--usr.sbin/lpr/Makefile9
-rw-r--r--usr.sbin/lpr/Makefile.inc7
-rw-r--r--usr.sbin/lpr/chkprintcap/Makefile12
-rw-r--r--usr.sbin/lpr/chkprintcap/Makefile.depend16
-rw-r--r--usr.sbin/lpr/chkprintcap/chkprintcap.897
-rw-r--r--usr.sbin/lpr/chkprintcap/chkprintcap.c324
-rw-r--r--usr.sbin/lpr/chkprintcap/skimprintcap.c262
-rw-r--r--usr.sbin/lpr/chkprintcap/skimprintcap.h45
-rw-r--r--usr.sbin/lpr/common_source/Makefile13
-rw-r--r--usr.sbin/lpr/common_source/Makefile.depend13
-rw-r--r--usr.sbin/lpr/common_source/common.c772
-rw-r--r--usr.sbin/lpr/common_source/ctlinfo.c916
-rw-r--r--usr.sbin/lpr/common_source/ctlinfo.h74
-rw-r--r--usr.sbin/lpr/common_source/displayq.c630
-rw-r--r--usr.sbin/lpr/common_source/lp.cdefs.h109
-rw-r--r--usr.sbin/lpr/common_source/lp.h316
-rw-r--r--usr.sbin/lpr/common_source/lp.local.h75
-rw-r--r--usr.sbin/lpr/common_source/matchjobs.c568
-rw-r--r--usr.sbin/lpr/common_source/matchjobs.h103
-rw-r--r--usr.sbin/lpr/common_source/net.c296
-rw-r--r--usr.sbin/lpr/common_source/pathnames.h47
-rw-r--r--usr.sbin/lpr/common_source/printcap.c439
-rw-r--r--usr.sbin/lpr/common_source/request.c79
-rw-r--r--usr.sbin/lpr/common_source/rmjob.c386
-rw-r--r--usr.sbin/lpr/common_source/startdaemon.c99
-rw-r--r--usr.sbin/lpr/filters.ru/Makefile7
-rw-r--r--usr.sbin/lpr/filters.ru/Makefile.depend10
-rw-r--r--usr.sbin/lpr/filters.ru/Makefile.inc3
-rw-r--r--usr.sbin/lpr/filters.ru/bjc-240.sh.sample64
-rw-r--r--usr.sbin/lpr/filters.ru/koi2855/Makefile7
-rw-r--r--usr.sbin/lpr/filters.ru/koi2855/Makefile.depend14
-rw-r--r--usr.sbin/lpr/filters.ru/koi2855/koi2855.c104
-rw-r--r--usr.sbin/lpr/filters.ru/koi2alt/Makefile7
-rw-r--r--usr.sbin/lpr/filters.ru/koi2alt/Makefile.depend14
-rw-r--r--usr.sbin/lpr/filters.ru/koi2alt/koi2alt.c105
-rw-r--r--usr.sbin/lpr/filters/Makefile9
-rw-r--r--usr.sbin/lpr/filters/Makefile.depend15
-rw-r--r--usr.sbin/lpr/filters/lpf.c206
-rw-r--r--usr.sbin/lpr/lp/Makefile7
-rw-r--r--usr.sbin/lpr/lp/Makefile.depend10
-rw-r--r--usr.sbin/lpr/lp/lp.1123
-rw-r--r--usr.sbin/lpr/lp/lp.sh82
-rw-r--r--usr.sbin/lpr/lpc/Makefile16
-rw-r--r--usr.sbin/lpr/lpc/Makefile.depend17
-rw-r--r--usr.sbin/lpr/lpc/cmds.c1314
-rw-r--r--usr.sbin/lpr/lpc/cmdtab.c84
-rw-r--r--usr.sbin/lpr/lpc/extern.h75
-rw-r--r--usr.sbin/lpr/lpc/lpc.8306
-rw-r--r--usr.sbin/lpr/lpc/lpc.c409
-rw-r--r--usr.sbin/lpr/lpc/lpc.h49
-rw-r--r--usr.sbin/lpr/lpc/movejobs.c271
-rw-r--r--usr.sbin/lpr/lpd/Makefile13
-rw-r--r--usr.sbin/lpr/lpd/Makefile.depend17
-rw-r--r--usr.sbin/lpr/lpd/extern.h45
-rw-r--r--usr.sbin/lpr/lpd/hosts.lpd3
-rw-r--r--usr.sbin/lpr/lpd/lpd.8351
-rw-r--r--usr.sbin/lpr/lpd/lpd.c934
-rw-r--r--usr.sbin/lpr/lpd/lpdchar.c1062
-rw-r--r--usr.sbin/lpr/lpd/modes.c225
-rw-r--r--usr.sbin/lpr/lpd/printcap52
-rw-r--r--usr.sbin/lpr/lpd/printjob.c2008
-rw-r--r--usr.sbin/lpr/lpd/recvjob.c393
-rw-r--r--usr.sbin/lpr/lpq/Makefile13
-rw-r--r--usr.sbin/lpr/lpq/Makefile.depend16
-rw-r--r--usr.sbin/lpr/lpq/lpq.1135
-rw-r--r--usr.sbin/lpr/lpq/lpq.c183
-rw-r--r--usr.sbin/lpr/lpr/Makefile18
-rw-r--r--usr.sbin/lpr/lpr/Makefile.depend16
-rw-r--r--usr.sbin/lpr/lpr/lpr.1318
-rw-r--r--usr.sbin/lpr/lpr/lpr.c865
-rw-r--r--usr.sbin/lpr/lpr/printcap.5431
-rw-r--r--usr.sbin/lpr/lprm/Makefile15
-rw-r--r--usr.sbin/lpr/lprm/Makefile.depend16
-rw-r--r--usr.sbin/lpr/lprm/lprm.1144
-rw-r--r--usr.sbin/lpr/lprm/lprm.c150
-rw-r--r--usr.sbin/lpr/lptest/Makefile6
-rw-r--r--usr.sbin/lpr/lptest/Makefile.depend14
-rw-r--r--usr.sbin/lpr/lptest/lptest.170
-rw-r--r--usr.sbin/lpr/lptest/lptest.c73
-rw-r--r--usr.sbin/lpr/pac/Makefile11
-rw-r--r--usr.sbin/lpr/pac/Makefile.depend16
-rw-r--r--usr.sbin/lpr/pac/pac.8102
-rw-r--r--usr.sbin/lpr/pac/pac.c435
83 files changed, 17185 insertions, 0 deletions
diff --git a/usr.sbin/lpr/Makefile b/usr.sbin/lpr/Makefile
new file mode 100644
index 000000000000..84b07538e0df
--- /dev/null
+++ b/usr.sbin/lpr/Makefile
@@ -0,0 +1,9 @@
+SUBDIR= common_source .WAIT \
+ chkprintcap lp lpc lpd lpq lpr lprm lptest pac \
+ filters filters.ru
+SUBDIR_PARALLEL=
+
+# Questions/ideas for lpr & friends could also be sent to:
+# freebsd-print@bostonradio.org
+
+.include <bsd.subdir.mk>
diff --git a/usr.sbin/lpr/Makefile.inc b/usr.sbin/lpr/Makefile.inc
new file mode 100644
index 000000000000..d72fbaacf631
--- /dev/null
+++ b/usr.sbin/lpr/Makefile.inc
@@ -0,0 +1,7 @@
+.include <src.opts.mk>
+
+.if ${MK_INET6_SUPPORT} != "no"
+CFLAGS+= -DINET6
+.endif
+
+.include "../Makefile.inc"
diff --git a/usr.sbin/lpr/chkprintcap/Makefile b/usr.sbin/lpr/chkprintcap/Makefile
new file mode 100644
index 000000000000..91f4c180f258
--- /dev/null
+++ b/usr.sbin/lpr/chkprintcap/Makefile
@@ -0,0 +1,12 @@
+.PATH: ${.CURDIR:H}/common_source
+
+PACKAGE=lp
+PROG= chkprintcap
+MAN= chkprintcap.8
+SRCS= chkprintcap.c skimprintcap.c
+
+CFLAGS+= -I${.CURDIR:H}/common_source
+
+LIBADD= lpr
+
+.include <bsd.prog.mk>
diff --git a/usr.sbin/lpr/chkprintcap/Makefile.depend b/usr.sbin/lpr/chkprintcap/Makefile.depend
new file mode 100644
index 000000000000..52597b984452
--- /dev/null
+++ b/usr.sbin/lpr/chkprintcap/Makefile.depend
@@ -0,0 +1,16 @@
+# Autogenerated - do NOT edit!
+
+DIRDEPS = \
+ include \
+ include/xlocale \
+ lib/${CSU_DIR} \
+ lib/libc \
+ lib/libcompiler_rt \
+ usr.sbin/lpr/common_source \
+
+
+.include <dirdeps.mk>
+
+.if ${DEP_RELDIR} == ${_DEP_RELDIR}
+# local dependencies - needed for -jN in clean tree
+.endif
diff --git a/usr.sbin/lpr/chkprintcap/chkprintcap.8 b/usr.sbin/lpr/chkprintcap/chkprintcap.8
new file mode 100644
index 000000000000..7aecf9fbb1b7
--- /dev/null
+++ b/usr.sbin/lpr/chkprintcap/chkprintcap.8
@@ -0,0 +1,97 @@
+.\" Copyright 1997 Massachusetts Institute of Technology
+.\"
+.\" Permission to use, copy, modify, and distribute this software and
+.\" its documentation for any purpose and without fee is hereby
+.\" granted, provided that both the above copyright notice and this
+.\" permission notice appear in all copies, that both the above
+.\" copyright notice and this permission notice appear in all
+.\" supporting documentation, and that the name of M.I.T. not be used
+.\" in advertising or publicity pertaining to distribution of the
+.\" software without specific, written prior permission. M.I.T. makes
+.\" no representations about the suitability of this software for any
+.\" purpose. It is provided "as is" without express or implied
+.\" warranty.
+.\"
+.\" THIS SOFTWARE IS PROVIDED BY M.I.T. ``AS IS''. M.I.T. DISCLAIMS
+.\" ALL EXPRESS OR IMPLIED WARRANTIES WITH REGARD TO THIS SOFTWARE,
+.\" INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+.\" MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. IN NO EVENT
+.\" SHALL M.I.T. 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.
+.Dd November 30, 1997
+.Dt CHKPRINTCAP 8
+.Os
+.Sh NAME
+.Nm chkprintcap
+.Nd check validity of entries in the print spooler database
+.Sh SYNOPSIS
+.Nm
+.Op Fl d
+.Op Fl f Ar printcap
+.Sh DESCRIPTION
+The
+.Nm
+utility scans a
+.Xr printcap 5
+database
+(named by the
+.Ar printcap
+argument, or by default
+.Pa /etc/printcap ) ,
+looking for entries which are invalid in one way or another.
+The following checks are currently implemented:
+.Bl -enum -offset indent
+.It
+.Sq Li tc=
+references were properly expanded
+.It
+.Sq Li tc=
+references did not form a loop
+.It
+No two printers share the same spool directory
+.Sq ( Li sd=
+capability).
+.El
+.Pp
+The
+.Nm
+utility exits with a status equal to the number of errors encountered before
+processing stopped.
+(In some cases, processing can stop before the
+entire file is scanned.)
+.Pp
+If the
+.Fl d
+flag is given,
+.Nm
+will attempt to create any missing spool directories, giving them
+.Sq Li u=rwx,go=rx
+(0755) mode, group
+.Sq Li daemon ,
+and the owner specified by the
+.Sq Li du=
+capability in the database (default 1, which corresponds to user
+.Sq Li daemon ) .
+.Sh SEE ALSO
+.Xr lpr 1 ,
+.Xr printcap 5 ,
+.Xr lpd 8
+.Sh AUTHORS
+The
+.Nm
+utility was written by
+.An Garrett A. Wollman Aq Mt wollman@lcs.mit.edu .
+.Sh BUGS
+Not enough sanity-checking is done.
+At a minimum, the ownership and
+mode of the spool directories should also be checked.
+Other
+parameters whose value could cause
+.Xr lpd 8
+to fail should be diagnosed.
diff --git a/usr.sbin/lpr/chkprintcap/chkprintcap.c b/usr.sbin/lpr/chkprintcap/chkprintcap.c
new file mode 100644
index 000000000000..5e9179b03f21
--- /dev/null
+++ b/usr.sbin/lpr/chkprintcap/chkprintcap.c
@@ -0,0 +1,324 @@
+/*
+ * Copyright 1997 Massachusetts Institute of Technology
+ *
+ * Permission to use, copy, modify, and distribute this software and
+ * its documentation for any purpose and without fee is hereby
+ * granted, provided that both the above copyright notice and this
+ * permission notice appear in all copies, that both the above
+ * copyright notice and this permission notice appear in all
+ * supporting documentation, and that the name of M.I.T. not be used
+ * in advertising or publicity pertaining to distribution of the
+ * software without specific, written prior permission. M.I.T. makes
+ * no representations about the suitability of this software for any
+ * purpose. It is provided "as is" without express or implied
+ * warranty.
+ *
+ * THIS SOFTWARE IS PROVIDED BY M.I.T. ``AS IS''. M.I.T. DISCLAIMS
+ * ALL EXPRESS OR IMPLIED WARRANTIES WITH REGARD TO THIS SOFTWARE,
+ * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. IN NO EVENT
+ * SHALL M.I.T. 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.
+ */
+
+static const char copyright[] =
+ "Copyright (C) 1997, Massachusetts Institute of Technology\r\n";
+
+#include "lp.cdefs.h" /* A cross-platform version of <sys/cdefs.h> */
+#include <sys/types.h>
+#include <sys/queue.h>
+#include <sys/stat.h>
+
+#include <err.h>
+#include <errno.h>
+#include <grp.h>
+#include <stdio.h>
+#include <string.h>
+#include <stdlib.h>
+#include <unistd.h>
+
+#include <sys/param.h> /* needed for lp.h but not used here */
+#include <dirent.h> /* ditto */
+#include "lp.h"
+#include "lp.local.h"
+#include "pathnames.h"
+#include "skimprintcap.h"
+
+static void check_spool_dirs(void);
+static int interpret_error(const struct printer *pp, int error);
+static void make_spool_dir(const struct printer *pp);
+static void note_spool_dir(const struct printer *pp, const struct stat *st);
+static void usage(void) __dead2;
+
+static int problems; /* number of problems encountered */
+
+/*
+ * chkprintcap - check the printcap file for syntactic and semantic errors
+ * Returns the number of problems found.
+ */
+int
+main(int argc, char **argv)
+{
+ struct skiminfo *skres;
+ char *pcap_fname;
+ int c, error, makedirs, more, queuecnt, verbosity;
+ struct printer myprinter, *pp;
+
+ makedirs = 0;
+ queuecnt = 0;
+ verbosity = 0;
+ pcap_fname = NULL;
+ pp = &myprinter;
+
+ while ((c = getopt(argc, argv, "df:v")) != -1) {
+ switch (c) {
+ case 'd':
+ makedirs = 1;
+ break;
+
+ case 'f':
+ pcap_fname = strdup(optarg);
+ setprintcap(pcap_fname);
+ break;
+
+ case 'v':
+ verbosity++;
+ break;
+
+ default:
+ usage();
+ }
+ }
+
+ if (optind != argc)
+ usage();
+
+ if (pcap_fname == NULL)
+ pcap_fname = strdup(_PATH_PRINTCAP);
+
+ /*
+ * Skim through the printcap file looking for simple user-mistakes
+ * which will produce the wrong result for the user, but which may
+ * be pretty hard for the user to notice. Such user-mistakes will
+ * only generate warning messages. The (fatal-) problem count will
+ * only be incremented if there is a system problem trying to read
+ * the printcap file.
+ */
+ skres = skim_printcap(pcap_fname, verbosity);
+ if (skres == NULL) {
+ problems = 1;
+ goto main_ret;
+ } else if (skres->fatalerr) {
+ problems = skres->fatalerr;
+ goto main_ret;
+ }
+
+ /*
+ * Now use the standard capability-db routines to check the values
+ * in each of the queues defined in the printcap file.
+ */
+ more = firstprinter(pp, &error);
+ if (interpret_error(pp, error) && more)
+ goto next;
+
+ while (more) {
+ struct stat stab;
+
+ queuecnt++;
+ errno = 0;
+ if (stat(pp->spool_dir, &stab) < 0) {
+ if (errno == ENOENT && makedirs) {
+ make_spool_dir(pp);
+ } else {
+ problems++;
+ warn("%s: %s", pp->printer, pp->spool_dir);
+ }
+ } else {
+ note_spool_dir(pp, &stab);
+ }
+
+ /* Make other queue-specific validity checks here... */
+
+next:
+ more = nextprinter(pp, &error);
+ if (interpret_error(pp, error) && more)
+ goto next;
+ }
+
+ check_spool_dirs();
+
+ if (queuecnt != skres->entries) {
+ warnx("WARNING: found %d entries when skimming %s,",
+ skres->entries, pcap_fname);
+ warnx("WARNING: but only found %d queues to process!",
+ queuecnt);
+ }
+
+main_ret:
+ free(pcap_fname);
+ return (problems);
+}
+
+/*
+ * Interpret the error code. Returns 1 if we should skip to the next
+ * record (as this record is unlikely to make sense). If the problem
+ * is very severe, exit. Otherwise, return zero.
+ */
+static int
+interpret_error(const struct printer *pp, int error)
+{
+ switch(error) {
+ case PCAPERR_OSERR:
+ err(++problems, "reading printer database");
+ case PCAPERR_TCLOOP:
+ ++problems;
+ warnx("%s: loop detected in tc= expansion", pp->printer);
+ return 1;
+ case PCAPERR_TCOPEN:
+ warnx("%s: unresolved tc= expansion", pp->printer);
+ return 1;
+ case PCAPERR_SUCCESS:
+ break;
+ default:
+ errx(++problems, "unknown printcap library error %d", error);
+ }
+ return 0;
+}
+
+/*
+ * Keep the list of spool directories. Note that we don't whine
+ * until all spool directories are noted, so that all of the more serious
+ * problems are noted first. We keep the list sorted by st_dev and
+ * st_ino, so that the problem spool directories can be noted in
+ * a single loop.
+ */
+struct dirlist {
+ LIST_ENTRY(dirlist) link;
+ struct stat stab;
+ char *path;
+ char *printer;
+};
+
+static LIST_HEAD(, dirlist) dirlist;
+
+static int
+lessp(const struct dirlist *a, const struct dirlist *b)
+{
+ if (a->stab.st_dev == b->stab.st_dev)
+ return a->stab.st_ino < b->stab.st_ino;
+ return a->stab.st_dev < b->stab.st_dev;
+}
+
+static int
+equal(const struct dirlist *a, const struct dirlist *b)
+{
+ return ((a->stab.st_dev == b->stab.st_dev)
+ && (a->stab.st_ino == b->stab.st_ino));
+}
+
+static void
+note_spool_dir(const struct printer *pp, const struct stat *st)
+{
+ struct dirlist *dp, *dp2, *last;
+
+ dp = malloc(sizeof *dp);
+ if (dp == NULL)
+ err(++problems, "malloc(%lu)", (u_long)sizeof *dp);
+
+ dp->stab = *st;
+ dp->printer = strdup(pp->printer);
+ if (dp->printer == 0)
+ err(++problems, "malloc(%lu)", strlen(pp->printer) + 1UL);
+ dp->path = strdup(pp->spool_dir);
+ if (dp->path == 0)
+ err(++problems, "malloc(%lu)", strlen(pp->spool_dir) + 1UL);
+
+ last = NULL;
+ LIST_FOREACH(dp2, &dirlist, link) {
+ if(!lessp(dp, dp2))
+ break;
+ last = dp2;
+ }
+
+ if (last) {
+ LIST_INSERT_AFTER(last, dp, link);
+ } else {
+ LIST_INSERT_HEAD(&dirlist, dp, link);
+ }
+}
+
+static void
+check_spool_dirs(void)
+{
+ struct dirlist *dp, *dp2;
+
+ for (dp = LIST_FIRST(&dirlist); dp; dp = dp2) {
+ dp2 = LIST_NEXT(dp, link);
+
+ if (dp2 != NULL && equal(dp, dp2)) {
+ ++problems;
+ if (strcmp(dp->path, dp2->path) == 0) {
+ warnx("%s and %s share the same spool, %s",
+ dp->printer, dp2->printer, dp->path);
+ } else {
+ warnx("%s (%s) and %s (%s) are the same "
+ "directory", dp->path, dp->printer,
+ dp2->path, dp2->printer);
+ }
+ continue;
+ }
+ /* Should probably check owners and modes here. */
+ }
+}
+
+#ifndef SPOOL_DIR_MODE
+#define SPOOL_DIR_MODE (S_IRUSR | S_IWUSR | S_IXUSR \
+ | S_IRGRP | S_IXGRP | S_IROTH | S_IXOTH)
+#endif
+
+static void
+make_spool_dir(const struct printer *pp)
+{
+ char *sd = pp->spool_dir;
+ struct group *gr;
+ struct stat stab;
+
+ if (mkdir(sd, S_IRUSR | S_IXUSR) < 0) {
+ problems++;
+ warn("%s: mkdir %s", pp->printer, sd);
+ return;
+ }
+ gr = getgrnam("daemon");
+ if (gr == NULL)
+ errx(++problems, "cannot locate daemon group");
+
+ if (chown(sd, pp->daemon_user, gr->gr_gid) < 0) {
+ ++problems;
+ warn("%s: cannot change ownership to %ld:%ld", sd,
+ (long)pp->daemon_user, (long)gr->gr_gid);
+ return;
+ }
+
+ if (chmod(sd, SPOOL_DIR_MODE) < 0) {
+ ++problems;
+ warn("%s: cannot change mode to %lo", sd, (long)SPOOL_DIR_MODE);
+ return;
+ }
+ if (stat(sd, &stab) < 0)
+ err(++problems, "stat: %s", sd);
+
+ note_spool_dir(pp, &stab);
+}
+
+static void
+usage(void)
+{
+ fprintf(stderr, "usage:\n\tchkprintcap [-dv] [-f printcapfile]\n");
+ exit(1);
+}
diff --git a/usr.sbin/lpr/chkprintcap/skimprintcap.c b/usr.sbin/lpr/chkprintcap/skimprintcap.c
new file mode 100644
index 000000000000..f6e74bc6df03
--- /dev/null
+++ b/usr.sbin/lpr/chkprintcap/skimprintcap.c
@@ -0,0 +1,262 @@
+/*-
+ * SPDX-License-Identifier: BSD-2-Clause
+ *
+ * ------+---------+---------+---------+---------+---------+---------+---------*
+ * Copyright (c) 2001 - Garance Alistair Drosehn <gad@FreeBSD.org>.
+ * All rights reserved.
+ *
+ * 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.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR 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 AUTHOR 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.
+ *
+ * The views and conclusions contained in the software and documentation
+ * are those of the authors and should not be interpreted as representing
+ * official policies, either expressed or implied, of the FreeBSD Project.
+ *
+ * ------+---------+---------+---------+---------+---------+---------+---------*
+ */
+
+#include "lp.cdefs.h" /* A cross-platform version of <sys/cdefs.h> */
+#include <sys/types.h>
+
+#include <ctype.h>
+#include <err.h>
+#include <errno.h>
+#include <grp.h>
+#include <stdio.h>
+#include <string.h>
+#include <stdlib.h>
+#include <unistd.h>
+
+#include <sys/param.h> /* needed for lp.h but not used here */
+#include <dirent.h> /* ditto */
+#include "lp.h"
+#include "lp.local.h"
+#include "skimprintcap.h"
+
+/*
+ * Save the canonical queue name of the entry that is currently being
+ * scanned, in case a warning message is printed for the current queue.
+ * Only the first 'QENTRY_MAXLEN' characters will be saved, since this
+ * is only for warning messages. The variable includes space for the
+ * string " (entry " and a trailing ")", when the scanner is in the
+ * middle of an entry. When the scanner is not in a specific entry,
+ * the variable will be the a null string.
+ */
+#define QENTRY_MAXLEN 30
+#define QENTRY_PREFIX " (entry "
+static char skim_entryname[sizeof(QENTRY_PREFIX) + QENTRY_MAXLEN + 2];
+
+/*
+ * isgraph is defined to work on an 'int', in the range 0 to 255, plus EOF.
+ * Define a wrapper which can take 'char', either signed or unsigned.
+ */
+#define isgraphch(Anychar) isgraph(((int) Anychar) & 255)
+
+struct skiminfo *
+skim_printcap(const char *pcap_fname, int verbosity)
+{
+ struct skiminfo *skinf;
+ char buff[BUFSIZ];
+ char *ch, *curline, *endfield, *lastchar;
+ FILE *pc_file;
+ int missing_nl;
+ enum {NO_CONTINUE, WILL_CONTINUE, BAD_CONTINUE} is_cont, had_cont;
+ enum {CMNT_LINE, ENTRY_LINE, TAB_LINE, TABERR_LINE} is_type, had_type;
+
+ skinf = malloc(sizeof(struct skiminfo));
+ if (skinf == NULL)
+ return (NULL);
+ memset(skinf, 0, sizeof(struct skiminfo));
+
+ pc_file = fopen(pcap_fname, "r");
+ if (pc_file == NULL) {
+ warn("fopen(%s)", pcap_fname);
+ skinf->fatalerr++;
+ return (skinf); /* fatal error */
+ }
+
+ skim_entryname[0] = '0';
+
+ is_cont = NO_CONTINUE;
+ is_type = CMNT_LINE;
+ errno = 0;
+ curline = fgets(buff, sizeof(buff), pc_file);
+ while (curline != NULL) {
+ skinf->lines++;
+
+ /* Check for the expected newline char, and remove it */
+ missing_nl = 0;
+ lastchar = strchr(curline, '\n');
+ if (lastchar != NULL)
+ *lastchar = '\0';
+ else {
+ lastchar = strchr(curline, '\0');
+ missing_nl = 1;
+ }
+ if (curline < lastchar)
+ lastchar--;
+
+ /*
+ * Check for `\' (continuation-character) at end of line.
+ * If there is none, then trim off spaces and check again.
+ * This would be a bad line because it looks like it is
+ * continued, but it will not be treated that way.
+ */
+ had_cont = is_cont;
+ is_cont = NO_CONTINUE;
+ if (*lastchar == '\\') {
+ is_cont = WILL_CONTINUE;
+ lastchar--;
+ } else {
+ while ((curline < lastchar) && !isgraphch(*lastchar))
+ lastchar--;
+ if (*lastchar == '\\')
+ is_cont = BAD_CONTINUE;
+ }
+
+ had_type = is_type;
+ is_type = CMNT_LINE;
+ switch (*curline) {
+ case '\0': /* treat zero-length line as comment */
+ case '#':
+ skinf->comments++;
+ break;
+ case ' ':
+ case '\t':
+ is_type = TAB_LINE;
+ break;
+ default:
+ is_type = ENTRY_LINE;
+ skinf->entries++;
+
+ /* pick up the queue name, to use in warning messages */
+ ch = curline;
+ while ((ch <= lastchar) && (*ch != ':') && (*ch != '|'))
+ ch++;
+ ch--; /* last char of queue name */
+ strcpy(skim_entryname, QENTRY_PREFIX);
+ if ((ch - curline) > QENTRY_MAXLEN) {
+ strncat(skim_entryname, curline, QENTRY_MAXLEN
+ - 1);
+ strcat(skim_entryname, "+");
+ } else {
+ strncat(skim_entryname, curline, (ch - curline
+ + 1));
+ }
+ strlcat(skim_entryname, ")", sizeof(skim_entryname));
+ break;
+ }
+
+ /*
+ * Check to see if the previous line was a bad contination
+ * line. The check is delayed until now so a warning message
+ * is not printed when a "bad continuation" is on a comment
+ * line, and it just "continues" into another comment line.
+ */
+ if (had_cont == BAD_CONTINUE) {
+ if ((had_type != CMNT_LINE) || (is_type != CMNT_LINE) ||
+ (verbosity > 1)) {
+ skinf->warnings++;
+ warnx("Warning: blanks after trailing '\\',"
+ " at line %d%s", skinf->lines - 1,
+ skim_entryname);
+ }
+ }
+
+ /* If we are no longer in an entry, then forget the name */
+ if ((had_cont != WILL_CONTINUE) && (is_type != ENTRY_LINE)) {
+ skim_entryname[0] = '\0';
+ }
+
+ /*
+ * Print out warning for missing newline, done down here
+ * so we are sure to have the right entry-name for it.
+ */
+ if (missing_nl) {
+ skinf->warnings++;
+ warnx("Warning: No newline at end of line %d%s",
+ skinf->lines, skim_entryname);
+ }
+
+ /*
+ * Check for start-of-entry lines which do not include a
+ * ":" character (to indicate the end of the name field).
+ * This can cause standard printcap processing to ignore
+ * ALL of the following lines.
+ * XXXXX - May need to allow for the list-of-names to
+ * continue on to the following line...
+ */
+ if (is_type == ENTRY_LINE) {
+ endfield = strchr(curline, ':');
+ if (endfield == NULL) {
+ skinf->warnings++;
+ warnx("Warning: No ':' to terminate name-field"
+ " at line %d%s", skinf->lines,
+ skim_entryname);
+ }
+ }
+
+ /*
+ * Now check for cases where this line is (or is-not) a
+ * continuation of the previous line, and a person skimming
+ * the file would assume it is not (or is) a continuation.
+ */
+ switch (had_cont) {
+ case NO_CONTINUE:
+ case BAD_CONTINUE:
+ if (is_type == TAB_LINE) {
+ skinf->warnings++;
+ warnx("Warning: values-line after line with"
+ " NO trailing '\\', at line %d%s",
+ skinf->lines, skim_entryname);
+ }
+ break;
+
+ case WILL_CONTINUE:
+ if (is_type == ENTRY_LINE) {
+ skinf->warnings++;
+ warnx("Warning: new entry starts after line"
+ " with trailing '\\', at line %d%s",
+ skinf->lines, skim_entryname);
+ }
+ break;
+ }
+
+ /* get another line from printcap and repeat loop */
+ curline = fgets(buff, sizeof(buff), pc_file);
+ }
+
+ if (errno != 0) {
+ warn("fgets(%s)", pcap_fname);
+ skinf->fatalerr++; /* fatal error */
+ }
+
+ if (skinf->warnings > 0)
+ warnx("%4d warnings from skimming %s", skinf->warnings,
+ pcap_fname);
+
+ if (verbosity)
+ warnx("%4d lines (%d comments), %d entries for %s",
+ skinf->lines, skinf->comments, skinf->entries, pcap_fname);
+
+ fclose(pc_file);
+ return (skinf);
+}
diff --git a/usr.sbin/lpr/chkprintcap/skimprintcap.h b/usr.sbin/lpr/chkprintcap/skimprintcap.h
new file mode 100644
index 000000000000..d7250ff6fff5
--- /dev/null
+++ b/usr.sbin/lpr/chkprintcap/skimprintcap.h
@@ -0,0 +1,45 @@
+/*-
+ * SPDX-License-Identifier: BSD-2-Clause
+ *
+ * ------+---------+---------+---------+---------+---------+---------+---------*
+ * Copyright (c) 2001 - Garance Alistair Drosehn <gad@FreeBSD.org>.
+ * All rights reserved.
+ *
+ * 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.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR 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 AUTHOR 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.
+ *
+ * The views and conclusions contained in the software and documentation
+ * are those of the authors and should not be interpreted as representing
+ * official policies, either expressed or implied, of the FreeBSD Project.
+ *
+ * ------+---------+---------+---------+---------+---------+---------+---------*
+ * ------+---------+---------+---------+---------+---------+---------+---------*
+ */
+
+struct skiminfo {
+ int comments;
+ int entries;
+ int fatalerr; /* fatal error, msg already printed */
+ int lines;
+ int warnings;
+};
+
+struct skiminfo *skim_printcap(const char *_pcap, int _verbosity);
diff --git a/usr.sbin/lpr/common_source/Makefile b/usr.sbin/lpr/common_source/Makefile
new file mode 100644
index 000000000000..a5d901a2d160
--- /dev/null
+++ b/usr.sbin/lpr/common_source/Makefile
@@ -0,0 +1,13 @@
+#
+# Library of internal routines for the print spooler suite.
+# Originally these were compiled separately into each program,
+# but the library makes it much easier to modularize them.
+#
+LIB= lpr
+INTERNALLIB=
+SRCS= common.c ctlinfo.c displayq.c matchjobs.c net.c \
+ printcap.c request.c rmjob.c startdaemon.c
+
+WARNS?= 1
+
+.include <bsd.lib.mk>
diff --git a/usr.sbin/lpr/common_source/Makefile.depend b/usr.sbin/lpr/common_source/Makefile.depend
new file mode 100644
index 000000000000..f2b0559818dd
--- /dev/null
+++ b/usr.sbin/lpr/common_source/Makefile.depend
@@ -0,0 +1,13 @@
+# Autogenerated - do NOT edit!
+
+DIRDEPS = \
+ include \
+ include/arpa \
+ include/xlocale \
+
+
+.include <dirdeps.mk>
+
+.if ${DEP_RELDIR} == ${_DEP_RELDIR}
+# local dependencies - needed for -jN in clean tree
+.endif
diff --git a/usr.sbin/lpr/common_source/common.c b/usr.sbin/lpr/common_source/common.c
new file mode 100644
index 000000000000..9b73667d3058
--- /dev/null
+++ b/usr.sbin/lpr/common_source/common.c
@@ -0,0 +1,772 @@
+/*-
+ * SPDX-License-Identifier: BSD-4-Clause
+ *
+ * Copyright (c) 1983, 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. All advertising materials mentioning features or use of this software
+ * must display the following acknowledgement:
+ * This product includes software developed by the University of
+ * California, Berkeley and its contributors.
+ * 4. 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 "lp.cdefs.h" /* A cross-platform version of <sys/cdefs.h> */
+#include <sys/param.h>
+#include <sys/stat.h>
+#include <sys/time.h>
+#include <sys/types.h>
+
+#include <ctype.h>
+#include <dirent.h>
+#include <err.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#include "lp.h"
+#include "lp.local.h"
+#include "pathnames.h"
+
+/*
+ * Routines and data common to all the line printer functions.
+ */
+char line[BUFSIZ];
+const char *progname; /* program name */
+
+static int compar(const void *_p1, const void *_p2);
+
+/*
+ * isdigit() takes a parameter of 'int', but expect values in the range
+ * of unsigned char. Define a wrapper which takes a value of type 'char',
+ * whether signed or unsigned, and ensure it ends up in the right range.
+ */
+#define isdigitch(Anychar) isdigit((u_char)(Anychar))
+
+/*
+ * get_line reads a line from the control file cfp, removes tabs, converts
+ * new-line to null and leaves it in line.
+ * Returns 0 at EOF or the number of characters read.
+ */
+int
+get_line(FILE *cfp)
+{
+ register int linel = 0;
+ register char *lp = line;
+ register int c;
+
+ while ((c = getc(cfp)) != '\n' && (size_t)(linel+1) < sizeof(line)) {
+ if (c == EOF)
+ return(0);
+ if (c == '\t') {
+ do {
+ *lp++ = ' ';
+ linel++;
+ } while ((linel & 07) != 0 && (size_t)(linel+1) <
+ sizeof(line));
+ continue;
+ }
+ *lp++ = c;
+ linel++;
+ }
+ *lp++ = '\0';
+ return(linel);
+}
+
+/*
+ * Scan the current directory and make a list of daemon files sorted by
+ * creation time.
+ * Return the number of entries and a pointer to the list.
+ */
+int
+getq(const struct printer *pp, struct jobqueue *(*namelist[]))
+{
+ register struct dirent *d;
+ register struct jobqueue *q, **queue;
+ size_t arraysz, entrysz, nitems;
+ struct stat stbuf;
+ DIR *dirp;
+ int statres;
+
+ PRIV_START
+ if ((dirp = opendir(pp->spool_dir)) == NULL) {
+ PRIV_END
+ return (-1);
+ }
+ if (fstat(dirfd(dirp), &stbuf) < 0)
+ goto errdone;
+ PRIV_END
+
+ /*
+ * Estimate the array size by taking the size of the directory file
+ * and dividing it by a multiple of the minimum size entry.
+ */
+ arraysz = (stbuf.st_size / 24);
+ if (arraysz < 16)
+ arraysz = 16;
+ queue = (struct jobqueue **)malloc(arraysz * sizeof(struct jobqueue *));
+ if (queue == NULL)
+ goto errdone;
+
+ nitems = 0;
+ while ((d = readdir(dirp)) != NULL) {
+ if (d->d_name[0] != 'c' || d->d_name[1] != 'f')
+ continue; /* daemon control files only */
+ PRIV_START
+ statres = stat(d->d_name, &stbuf);
+ PRIV_END
+ if (statres < 0)
+ continue; /* Doesn't exist */
+ entrysz = sizeof(struct jobqueue) - sizeof(q->job_cfname) +
+ strlen(d->d_name) + 1;
+ q = (struct jobqueue *)malloc(entrysz);
+ if (q == NULL)
+ goto errdone;
+ q->job_matched = 0;
+ q->job_processed = 0;
+ q->job_time = stbuf.st_mtime;
+ strcpy(q->job_cfname, d->d_name);
+ /*
+ * Check to make sure the array has space left and
+ * realloc the maximum size.
+ */
+ if (++nitems > arraysz) {
+ queue = (struct jobqueue **)reallocarray((char *)queue,
+ arraysz, 2 * sizeof(struct jobqueue *));
+ if (queue == NULL) {
+ free(q);
+ goto errdone;
+ }
+ arraysz *= 2;
+ }
+ queue[nitems-1] = q;
+ }
+ closedir(dirp);
+ if (nitems)
+ qsort(queue, nitems, sizeof(struct jobqueue *), compar);
+ *namelist = queue;
+ return(nitems);
+
+errdone:
+ closedir(dirp);
+ PRIV_END
+ return (-1);
+}
+
+/*
+ * Compare modification times.
+ */
+static int
+compar(const void *p1, const void *p2)
+{
+ const struct jobqueue *qe1, *qe2;
+
+ qe1 = *(const struct jobqueue * const *)p1;
+ qe2 = *(const struct jobqueue * const *)p2;
+
+ if (qe1->job_time < qe2->job_time)
+ return (-1);
+ if (qe1->job_time > qe2->job_time)
+ return (1);
+ /*
+ * At this point, the two files have the same last-modification time.
+ * return a result based on filenames, so that 'cfA001some.host' will
+ * come before 'cfA002some.host'. Since the jobid ('001') will wrap
+ * around when it gets to '999', we also assume that '9xx' jobs are
+ * older than '0xx' jobs.
+ */
+ if ((qe1->job_cfname[3] == '9') && (qe2->job_cfname[3] == '0'))
+ return (-1);
+ if ((qe1->job_cfname[3] == '0') && (qe2->job_cfname[3] == '9'))
+ return (1);
+ return (strcmp(qe1->job_cfname, qe2->job_cfname));
+}
+
+/*
+ * A simple routine to determine the job number for a print job based on
+ * the name of its control file. The algorithm used here may look odd, but
+ * the main issue is that all parts of `lpd', `lpc', `lpq' & `lprm' must be
+ * using the same algorithm, whatever that algorithm may be. If the caller
+ * provides a non-null value for ''hostpp', then this returns a pointer to
+ * the start of the hostname (or IP address?) as found in the filename.
+ *
+ * Algorithm: The standard `cf' file has the job number start in position 4,
+ * but some implementations have that as an extra file-sequence letter, and
+ * start the job number in position 5. The job number is usually three bytes,
+ * but may be as many as five. Confusing matters still more, some Windows
+ * print servers will append an IP address to the job number, instead of
+ * the expected hostname. So, if the job number ends with a '.', then
+ * assume the correct jobnum value is the first three digits.
+ */
+int
+calc_jobnum(const char *cfname, const char **hostpp)
+{
+ int jnum;
+ const char *cp, *numstr, *hoststr;
+
+ numstr = cfname + 3;
+ if (!isdigitch(*numstr))
+ numstr++;
+ jnum = 0;
+ for (cp = numstr; (cp < numstr + 5) && isdigitch(*cp); cp++)
+ jnum = jnum * 10 + (*cp - '0');
+ hoststr = cp;
+
+ /*
+ * If the filename was built with an IP number instead of a hostname,
+ * then recalculate using only the first three digits found.
+ */
+ while(isdigitch(*cp))
+ cp++;
+ if (*cp == '.') {
+ jnum = 0;
+ for (cp = numstr; (cp < numstr + 3) && isdigitch(*cp); cp++)
+ jnum = jnum * 10 + (*cp - '0');
+ hoststr = cp;
+ }
+ if (hostpp != NULL)
+ *hostpp = hoststr;
+ return (jnum);
+}
+
+/* sleep n milliseconds */
+void
+delay(int millisec)
+{
+ struct timeval tdelay;
+
+ if (millisec <= 0 || millisec > 10000)
+ fatal((struct printer *)0, /* fatal() knows how to deal */
+ "unreasonable delay period (%d)", millisec);
+ tdelay.tv_sec = millisec / 1000;
+ tdelay.tv_usec = millisec * 1000 % 1000000;
+ (void) select(0, (fd_set *)0, (fd_set *)0, (fd_set *)0, &tdelay);
+}
+
+char *
+lock_file_name(const struct printer *pp, char *buf, size_t len)
+{
+ static char staticbuf[MAXPATHLEN];
+
+ if (buf == NULL)
+ buf = staticbuf;
+ if (len == 0)
+ len = MAXPATHLEN;
+
+ if (pp->lock_file[0] == '/')
+ strlcpy(buf, pp->lock_file, len);
+ else
+ snprintf(buf, len, "%s/%s", pp->spool_dir, pp->lock_file);
+
+ return buf;
+}
+
+char *
+status_file_name(const struct printer *pp, char *buf, size_t len)
+{
+ static char staticbuf[MAXPATHLEN];
+
+ if (buf == NULL)
+ buf = staticbuf;
+ if (len == 0)
+ len = MAXPATHLEN;
+
+ if (pp->status_file[0] == '/')
+ strlcpy(buf, pp->status_file, len);
+ else
+ snprintf(buf, len, "%s/%s", pp->spool_dir, pp->status_file);
+
+ return buf;
+}
+
+/*
+ * Routine to change operational state of a print queue. The operational
+ * state is indicated by the access bits on the lock file for the queue.
+ * At present, this is only called from various routines in lpc/cmds.c.
+ *
+ * XXX - Note that this works by changing access-bits on the
+ * file, and you can only do that if you are the owner of
+ * the file, or root. Thus, this won't really work for
+ * userids in the "LPR_OPER" group, unless lpc is running
+ * setuid to root (or maybe setuid to daemon).
+ * Generally lpc is installed setgid to daemon, but does
+ * not run setuid.
+ */
+int
+set_qstate(int action, const char *lfname)
+{
+ struct stat stbuf;
+ mode_t chgbits, newbits, oldmask;
+ const char *failmsg, *okmsg;
+ static const char *nomsg = "no state msg";
+ int chres, errsav, fd, res, statres;
+
+ /*
+ * Find what the current access-bits are.
+ */
+ memset(&stbuf, 0, sizeof(stbuf));
+ PRIV_START
+ statres = stat(lfname, &stbuf);
+ errsav = errno;
+ PRIV_END
+ if ((statres < 0) && (errsav != ENOENT)) {
+ printf("\tcannot stat() lock file\n");
+ return (SQS_STATFAIL);
+ /* NOTREACHED */
+ }
+
+ /*
+ * Determine which bit(s) should change for the requested action.
+ */
+ chgbits = stbuf.st_mode;
+ newbits = LOCK_FILE_MODE;
+ okmsg = NULL;
+ failmsg = NULL;
+ if (action & SQS_QCHANGED) {
+ chgbits |= LFM_RESET_QUE;
+ newbits |= LFM_RESET_QUE;
+ /* The okmsg is not actually printed for this case. */
+ okmsg = nomsg;
+ failmsg = "set queue-changed";
+ }
+ if (action & SQS_DISABLEQ) {
+ chgbits |= LFM_QUEUE_DIS;
+ newbits |= LFM_QUEUE_DIS;
+ okmsg = "queuing disabled";
+ failmsg = "disable queuing";
+ }
+ if (action & SQS_STOPP) {
+ chgbits |= LFM_PRINT_DIS;
+ newbits |= LFM_PRINT_DIS;
+ okmsg = "printing disabled";
+ failmsg = "disable printing";
+ if (action & SQS_DISABLEQ) {
+ okmsg = "printer and queuing disabled";
+ failmsg = "disable queuing and printing";
+ }
+ }
+ if (action & SQS_ENABLEQ) {
+ chgbits &= ~LFM_QUEUE_DIS;
+ newbits &= ~LFM_QUEUE_DIS;
+ okmsg = "queuing enabled";
+ failmsg = "enable queuing";
+ }
+ if (action & SQS_STARTP) {
+ chgbits &= ~LFM_PRINT_DIS;
+ newbits &= ~LFM_PRINT_DIS;
+ okmsg = "printing enabled";
+ failmsg = "enable printing";
+ }
+ if (okmsg == NULL) {
+ /* This routine was called with an invalid action. */
+ printf("\t<error in set_qstate!>\n");
+ return (SQS_PARMERR);
+ /* NOTREACHED */
+ }
+
+ res = 0;
+ if (statres >= 0) {
+ /* The file already exists, so change the access. */
+ PRIV_START
+ chres = chmod(lfname, chgbits);
+ errsav = errno;
+ PRIV_END
+ res = SQS_CHGOK;
+ if (chres < 0)
+ res = SQS_CHGFAIL;
+ } else if (newbits == LOCK_FILE_MODE) {
+ /*
+ * The file does not exist, but the state requested is
+ * the same as the default state when no file exists.
+ * Thus, there is no need to create the file.
+ */
+ res = SQS_SKIPCREOK;
+ } else {
+ /*
+ * The file did not exist, so create it with the
+ * appropriate access bits for the requested action.
+ * Push a new umask around that create, to make sure
+ * all the read/write bits are set as desired.
+ */
+ oldmask = umask(S_IWOTH);
+ PRIV_START
+ fd = open(lfname, O_WRONLY|O_CREAT, newbits);
+ errsav = errno;
+ PRIV_END
+ umask(oldmask);
+ res = SQS_CREFAIL;
+ if (fd >= 0) {
+ res = SQS_CREOK;
+ close(fd);
+ }
+ }
+
+ switch (res) {
+ case SQS_CHGOK:
+ case SQS_CREOK:
+ case SQS_SKIPCREOK:
+ if (okmsg != nomsg)
+ printf("\t%s\n", okmsg);
+ break;
+ case SQS_CREFAIL:
+ printf("\tcannot create lock file: %s\n",
+ strerror(errsav));
+ break;
+ default:
+ printf("\tcannot %s: %s\n", failmsg, strerror(errsav));
+ break;
+ }
+
+ return (res);
+}
+
+/* routine to get a current timestamp, optionally in a standard-fmt string */
+void
+lpd_gettime(struct timespec *tsp, char *strp, size_t strsize)
+{
+ struct timespec local_ts;
+ struct timeval btime;
+ char tempstr[TIMESTR_SIZE];
+#ifdef STRFTIME_WRONG_z
+ char *destp;
+#endif
+
+ if (tsp == NULL)
+ tsp = &local_ts;
+
+ /* some platforms have a routine called clock_gettime, but the
+ * routine does nothing but return "not implemented". */
+ memset(tsp, 0, sizeof(struct timespec));
+ if (clock_gettime(CLOCK_REALTIME, tsp)) {
+ /* nanosec-aware rtn failed, fall back to microsec-aware rtn */
+ memset(tsp, 0, sizeof(struct timespec));
+ gettimeofday(&btime, NULL);
+ tsp->tv_sec = btime.tv_sec;
+ tsp->tv_nsec = btime.tv_usec * 1000;
+ }
+
+ /* caller may not need a character-ized version */
+ if ((strp == NULL) || (strsize < 1))
+ return;
+
+ strftime(tempstr, TIMESTR_SIZE, LPD_TIMESTAMP_PATTERN,
+ localtime(&tsp->tv_sec));
+
+ /*
+ * This check is for implementations of strftime which treat %z
+ * (timezone as [+-]hhmm ) like %Z (timezone as characters), or
+ * completely ignore %z. This section is not needed on freebsd.
+ * I'm not sure this is completely right, but it should work OK
+ * for EST and EDT...
+ */
+#ifdef STRFTIME_WRONG_z
+ destp = strrchr(tempstr, ':');
+ if (destp != NULL) {
+ destp += 3;
+ if ((*destp != '+') && (*destp != '-')) {
+ char savday[6];
+ int tzmin = timezone / 60;
+ int tzhr = tzmin / 60;
+ if (daylight)
+ tzhr--;
+ strcpy(savday, destp + strlen(destp) - 4);
+ snprintf(destp, (destp - tempstr), "%+03d%02d",
+ (-1*tzhr), tzmin % 60);
+ strcat(destp, savday);
+ }
+ }
+#endif
+
+ if (strsize > TIMESTR_SIZE) {
+ strsize = TIMESTR_SIZE;
+ strp[TIMESTR_SIZE+1] = '\0';
+ }
+ strlcpy(strp, tempstr, strsize);
+}
+
+/* routines for writing transfer-statistic records */
+void
+trstat_init(struct printer *pp, const char *fname, int filenum)
+{
+ register const char *srcp;
+ register char *destp, *endp;
+
+ /*
+ * Figure out the job id of this file. The filename should be
+ * 'cf', 'df', or maybe 'tf', followed by a letter (or sometimes
+ * two), followed by the jobnum, followed by a hostname.
+ * The jobnum is usually 3 digits, but might be as many as 5.
+ * Note that some care has to be taken parsing this, as the
+ * filename could be coming from a remote-host, and thus might
+ * not look anything like what is expected...
+ */
+ memset(pp->jobnum, 0, sizeof(pp->jobnum));
+ pp->jobnum[0] = '0';
+ srcp = strchr(fname, '/');
+ if (srcp == NULL)
+ srcp = fname;
+ destp = &(pp->jobnum[0]);
+ endp = destp + 5;
+ while (*srcp != '\0' && (*srcp < '0' || *srcp > '9'))
+ srcp++;
+ while (*srcp >= '0' && *srcp <= '9' && destp < endp)
+ *(destp++) = *(srcp++);
+
+ /* get the starting time in both numeric and string formats, and
+ * save those away along with the file-number */
+ pp->jobdfnum = filenum;
+ lpd_gettime(&pp->tr_start, pp->tr_timestr, (size_t)TIMESTR_SIZE);
+}
+
+void
+trstat_write(struct printer *pp, tr_sendrecv sendrecv, size_t bytecnt,
+ const char *userid, const char *otherhost, const char *orighost)
+{
+#define STATLINE_SIZE 1024
+ double trtime;
+ size_t remspace;
+ int statfile;
+ char thishost[MAXHOSTNAMELEN], statline[STATLINE_SIZE];
+ char *eostat;
+ const char *lprhost, *recvdev, *recvhost, *rectype;
+ const char *sendhost, *statfname;
+#define UPD_EOSTAT(xStr) do { \
+ eostat = strchr(xStr, '\0'); \
+ remspace = eostat - xStr; \
+} while(0)
+
+ lpd_gettime(&pp->tr_done, NULL, (size_t)0);
+ trtime = DIFFTIME_TS(pp->tr_done, pp->tr_start);
+
+ gethostname(thishost, sizeof(thishost));
+ lprhost = sendhost = recvhost = recvdev = NULL;
+ switch (sendrecv) {
+ case TR_SENDING:
+ rectype = "send";
+ statfname = pp->stat_send;
+ sendhost = thishost;
+ recvhost = otherhost;
+ break;
+ case TR_RECVING:
+ rectype = "recv";
+ statfname = pp->stat_recv;
+ sendhost = otherhost;
+ recvhost = thishost;
+ break;
+ case TR_PRINTING:
+ /*
+ * This case is for copying to a device (presumably local,
+ * though filters using things like 'net/CAP' can confuse
+ * this assumption...).
+ */
+ rectype = "prnt";
+ statfname = pp->stat_send;
+ sendhost = thishost;
+ recvdev = _PATH_DEFDEVLP;
+ if (pp->lp) recvdev = pp->lp;
+ break;
+ default:
+ /* internal error... should we syslog/printf an error? */
+ return;
+ }
+ if (statfname == NULL)
+ return;
+
+ /*
+ * the original-host and userid are found out by reading thru the
+ * cf (control-file) for the job. Unfortunately, on incoming jobs
+ * the df's (data-files) are sent before the matching cf, so the
+ * orighost & userid are generally not-available for incoming jobs.
+ *
+ * (it would be nice to create a work-around for that..)
+ */
+ if (orighost && (*orighost != '\0'))
+ lprhost = orighost;
+ else
+ lprhost = ".na.";
+ if (*userid == '\0')
+ userid = NULL;
+
+ /*
+ * Format of statline.
+ * Some of the keywords listed here are not implemented here, but
+ * they are listed to reserve the meaning for a given keyword.
+ * Fields are separated by a blank. The fields in statline are:
+ * <tstamp> - time the transfer started
+ * <ptrqueue> - name of the printer queue (the short-name...)
+ * <hname> - hostname the file originally came from (the
+ * 'lpr host'), if known, or "_na_" if not known.
+ * <xxx> - id of job from that host (generally three digits)
+ * <n> - file count (# of file within job)
+ * <rectype> - 4-byte field indicating the type of transfer
+ * statistics record. "send" means it's from the
+ * host sending a datafile, "recv" means it's from
+ * a host as it receives a datafile.
+ * user=<userid> - user who sent the job (if known)
+ * secs=<n> - seconds it took to transfer the file
+ * bytes=<n> - number of bytes transferred (ie, "bytecount")
+ * bps=<n.n>e<n> - Bytes/sec (if the transfer was "big enough"
+ * for this to be useful)
+ * ! top=<str> - type of printer (if the type is defined in
+ * printcap, and if this statline is for sending
+ * a file to that ptr)
+ * ! qls=<n> - queue-length at start of send/print-ing a job
+ * ! qle=<n> - queue-length at end of send/print-ing a job
+ * sip=<addr> - IP address of sending host, only included when
+ * receiving a job.
+ * shost=<hname> - sending host (if that does != the original host)
+ * rhost=<hname> - hostname receiving the file (ie, "destination")
+ * rdev=<dev> - device receiving the file, when the file is being
+ * send to a device instead of a remote host.
+ *
+ * Note: A single print job may be transferred multiple times. The
+ * original 'lpr' occurs on one host, and that original host might
+ * send to some interim host (or print server). That interim host
+ * might turn around and send the job to yet another host (most likely
+ * the real printer). The 'shost=' parameter is only included if the
+ * sending host for this particular transfer is NOT the same as the
+ * host which did the original 'lpr'.
+ *
+ * Many values have 'something=' tags before them, because they are
+ * in some sense "optional", or their order may vary. "Optional" may
+ * mean in the sense that different SITES might choose to have other
+ * fields in the record, or that some fields are only included under
+ * some circumstances. Programs processing these records should not
+ * assume the order or existence of any of these keyword fields.
+ */
+ snprintf(statline, STATLINE_SIZE, "%s %s %s %s %03ld %s",
+ pp->tr_timestr, pp->printer, lprhost, pp->jobnum,
+ pp->jobdfnum, rectype);
+ UPD_EOSTAT(statline);
+
+ if (userid != NULL) {
+ snprintf(eostat, remspace, " user=%s", userid);
+ UPD_EOSTAT(statline);
+ }
+ snprintf(eostat, remspace, " secs=%#.2f bytes=%lu", trtime,
+ (unsigned long)bytecnt);
+ UPD_EOSTAT(statline);
+
+ /*
+ * The bps field duplicates info from bytes and secs, so do
+ * not bother to include it for very small files.
+ */
+ if ((bytecnt > 25000) && (trtime > 1.1)) {
+ snprintf(eostat, remspace, " bps=%#.2e",
+ ((double)bytecnt/trtime));
+ UPD_EOSTAT(statline);
+ }
+
+ if (sendrecv == TR_RECVING) {
+ if (remspace > 5+strlen(from_ip) ) {
+ snprintf(eostat, remspace, " sip=%s", from_ip);
+ UPD_EOSTAT(statline);
+ }
+ }
+ if (0 != strcmp(lprhost, sendhost)) {
+ if (remspace > 7+strlen(sendhost) ) {
+ snprintf(eostat, remspace, " shost=%s", sendhost);
+ UPD_EOSTAT(statline);
+ }
+ }
+ if (recvhost) {
+ if (remspace > 7+strlen(recvhost) ) {
+ snprintf(eostat, remspace, " rhost=%s", recvhost);
+ UPD_EOSTAT(statline);
+ }
+ }
+ if (recvdev) {
+ if (remspace > 6+strlen(recvdev) ) {
+ snprintf(eostat, remspace, " rdev=%s", recvdev);
+ UPD_EOSTAT(statline);
+ }
+ }
+ if (remspace > 1) {
+ strcpy(eostat, "\n");
+ } else {
+ /* probably should back up to just before the final " x=".. */
+ strcpy(statline+STATLINE_SIZE-2, "\n");
+ }
+ statfile = open(statfname, O_WRONLY|O_APPEND, 0664);
+ if (statfile < 0) {
+ /* statfile was given, but we can't open it. should we
+ * syslog/printf this as an error? */
+ return;
+ }
+ write(statfile, statline, strlen(statline));
+ close(statfile);
+
+ return;
+#undef UPD_EOSTAT
+}
+
+#include <stdarg.h>
+
+void
+fatal(const struct printer *pp, const char *msg, ...)
+{
+ va_list ap;
+ va_start(ap, msg);
+ /* this error message is being sent to the 'from_host' */
+ if (from_host != local_host)
+ (void)printf("%s: ", local_host);
+ (void)printf("%s: ", progname);
+ if (pp && pp->printer)
+ (void)printf("%s: ", pp->printer);
+ (void)vprintf(msg, ap);
+ va_end(ap);
+ (void)putchar('\n');
+ exit(1);
+}
+
+/*
+ * Close all file descriptors from START on up.
+ */
+void
+closeallfds(int start)
+{
+ int stop;
+
+ if (USE_CLOSEFROM) /* The faster, modern solution */
+ closefrom(start);
+ else {
+ /* This older logic can be pretty awful on some OS's. The
+ * getdtablesize() might return ``infinity'', and then this
+ * will waste a lot of time closing file descriptors which
+ * had never been open()-ed. */
+ stop = getdtablesize();
+ for (; start < stop; start++)
+ close(start);
+ }
+}
+
diff --git a/usr.sbin/lpr/common_source/ctlinfo.c b/usr.sbin/lpr/common_source/ctlinfo.c
new file mode 100644
index 000000000000..5f5351b613c2
--- /dev/null
+++ b/usr.sbin/lpr/common_source/ctlinfo.c
@@ -0,0 +1,916 @@
+/*-
+ * SPDX-License-Identifier: BSD-2-Clause
+ *
+ * ------+---------+---------+---------+---------+---------+---------+---------*
+ * Copyright (c) 2001,2011 - Garance Alistair Drosehn <gad@FreeBSD.org>.
+ * All rights reserved.
+ *
+ * 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.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR 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 AUTHOR 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.
+ *
+ * The views and conclusions contained in the software and documentation
+ * are those of the authors and should not be interpreted as representing
+ * official policies, either expressed or implied, of the FreeBSD Project.
+ *
+ * ------+---------+---------+---------+---------+---------+---------+---------*
+ */
+
+#include "lp.cdefs.h" /* A cross-platform version of <sys/cdefs.h> */
+/*
+ * ctlinfo - This collection of routines will know everything there is to
+ * know about the information inside a control file ('cf*') which is used
+ * to describe a print job in lpr & friends. The eventual goal is that it
+ * will be the ONLY source file to know what's inside these control-files.
+ */
+
+/*
+ * Some define's useful for debugging.
+ * TRIGGERTEST_FNAME and DEBUGREADCF_FNAME, allow us to do testing on
+ * a per-spool-directory basis.
+ */
+/* #define TRIGGERTEST_FNAME "LpdTestRenameTF" */
+/* #define DEBUGREADCF_FNAME "LpdDebugReadCF" */
+/* #define LEAVE_TMPCF_FILES 1 */
+
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <ctype.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <limits.h>
+#include <netdb.h>
+#include <pwd.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <syslog.h>
+#include <unistd.h>
+#include "ctlinfo.h"
+
+struct cjprivate {
+ struct cjobinfo pub;
+ char *cji_buff; /* buffer for getline */
+ char *cji_eobuff; /* last byte IN the buffer */
+ FILE *cji_fstream;
+ int cji_buffsize; /* # bytes in the buffer */
+ int cji_dumpit;
+};
+
+/*
+ * All the following take a parameter of 'int', but expect values in the
+ * range of unsigned char. Define wrappers which take values of type 'char',
+ * whether signed or unsigned, and ensure they end up in the right range.
+ */
+#define isdigitch(Anychar) isdigit((u_char)(Anychar))
+#define islowerch(Anychar) islower((u_char)(Anychar))
+#define isupperch(Anychar) isupper((u_char)(Anychar))
+#define tolowerch(Anychar) tolower((u_char)(Anychar))
+
+#define OTHER_USERID_CHARS "-_" /* special chars valid in a userid */
+
+#define roundup(x, y) ((((x)+((y)-1))/(y))*(y))
+
+/*
+ * This has to be large enough to fit the maximum length of a single line
+ * in a control-file, including the leading 'command id', a trailing '\n'
+ * and ending '\0'. The max size of an 'U'nlink line, for instance, is
+ * 1 ('U') + PATH_MAX (filename) + 2 ('\n\0'). The maximum 'H'ost line is
+ * 1 ('H') + NI_MAXHOST (remote hostname) + 2 ('\n\0'). Other lines can be
+ * even longer than those. So, pick some nice, large, arbitrary value.
+ */
+#define CTI_LINEMAX PATH_MAX+NI_MAXHOST+5
+
+extern const char *from_host; /* client's machine name */
+extern const char *from_ip; /* client machine's IP address */
+
+__BEGIN_DECLS
+void ctl_dumpcji(FILE *_dbg_stream, const char *_heading,
+ struct cjobinfo *_cjinf);
+static char *ctl_getline(struct cjobinfo *_cjinf);
+static void ctl_rewindcf(struct cjobinfo *_cjinf);
+char *ctl_rmjob(const char *_ptrname, const char *_cfname);
+__END_DECLS
+
+/*
+ * Here are some things which might be needed when compiling this under
+ * platforms other than FreeBSD.
+ */
+#ifndef __FreeBSD__
+# ifndef NAME_MAX
+# define NAME_MAX 255
+# endif
+# ifndef NI_MAXHOST
+# define NI_MAXHOST 1025
+# endif
+# ifndef PATH_MAX
+# define PATH_MAX 1024
+# endif
+__BEGIN_DECLS
+char *strdup(const char *_src);
+size_t strlcpy(char *_dst, const char *_src, size_t _siz);
+__END_DECLS
+#endif
+
+/*
+ * Control-files (cf*) have the following format.
+ *
+ * Each control-file describes a single job. It will list one or more
+ * "datafiles" (df*) which should be copied to some printer. Usually
+ * there is only one datafile per job. For the curious, RFC 1179 is an
+ * informal and out-of-date description of lpr/lpd circa 1990.
+ *
+ * Each line in the file gives an attribute of the job as a whole, or one
+ * of the datafiles in the job, or a "command" indicating something to do
+ * with one of the datafiles. Each line starts with an 'id' that indicates
+ * what that line is there for. The 'id' is historically a single byte,
+ * but may be multiple bytes (obviously it would be best if multi-byte ids
+ * started with some letter not already used as a single-byte id!).
+ * After the 'id', the remainder of the line will be the value of the
+ * indicated attribute, or a name of the datafile to be operated on.
+ *
+ * In the following lists of ids, the ids with a '!' in front of them are
+ * NOT explicitly supported by this version of lpd, or at least "not yet
+ * supported". They are only listed for reference purposes, so people
+ * won't be tempted to reuse the same id for a different purpose.
+ *
+ * The following are attributes of the job which should not appear more
+ * than once in a control file. Only the 'H' and 'P' lines are required
+ * by the RFC, but some implementations of lpr won't even get that right.
+ *
+ * ! A - [used by lprNG]
+ * B - As far as I know, this is never used as a single-byte id.
+ * Therefore, I intend to use it for multi-byte id codes.
+ * C - "class name" to display on banner page (this is sometimes
+ * used to hold options for print filters)
+ * ! D - [in lprNG, "timestamp" of when the job was submitted]
+ * ! E - "environment variables" to set [some versions of linux]
+ * H - "host name" of machine where the original 'lpr' was done
+ * I - "indent", the amount to indent output
+ * J - "job name" to display on banner page
+ * L - "literal" user's name as it should be displayed on the
+ * banner page (it is the existence of an 'L' line which
+ * indicates that a job should have a banner page).
+ * M - "mail", userid to mail to when done printing (with email
+ * going to 'M'@'H', so to speak).
+ * P - "person", the user's login name (e.g. for accounting)
+ * ! Q - [used by lprNG for queue-name]
+ * R - "resolution" in dpi, for some laser printer queues
+ * T - "title" for files sent thru 'pr'
+ * W - "width" to use for printing plain-text files
+ * Z - In BSD, "locale" to use for datafiles sent thru 'pr'.
+ * (this BSD usage should move to a different id...)
+ * [in lprNG - this line holds the "Z options"]
+ * 1 - "R font file" for files sent thru troff
+ * 2 - "I font file" for files sent thru troff
+ * 3 - "B font file" for files sent thru troff
+ * 4 - "S font file" for files sent thru troff
+ *
+ * The following are attributes attached to a datafile, and thus may
+ * appear multiple times in a control file (once per datafile):
+ *
+ * N - "name" of file (for display purposes, used by 'lpq')
+ * S - "stat() info" used for symbolic link ('lpr -s')
+ * security checks.
+ *
+ * The following indicate actions to take on a given datafile. The same
+ * datafile may appear on more than one "print this file" command in the
+ * control file. Note that ALL ids with lowercase letters are expected
+ * to be actions to "print this file":
+ *
+ * c - "file name", cifplot file to print. This action appears
+ * when the user has requested 'lpr -c'.
+ * d - "file name", dvi file to print, user requested 'lpr -d'
+ * f - "file name", a plain-text file to print = "standard"
+ * g - "file name", plot(1G) file to print, ie 'lpr -g'
+ * l - "file name", text file with control chars which should
+ * be printed literally, ie 'lpr -l' (note: some printers
+ * take this id as a request to print a postscript file,
+ * and because of *that* some OS's use 'l' to indicate
+ * that a datafile is a postscript file)
+ * n - "file name", ditroff(1) file to print, ie 'lpr -n'
+ * o - "file name", a postscript file to print. This id is
+ * described in the original RFC, but not much has been
+ * done with it. This 'lpr' does not generate control
+ * lines with 'o'-actions, but lpd's printjob processing
+ * will treat it the same as 'l'.
+ * p - "file name", text file to print with pr(1), ie 'lpr -p'
+ * t - "file name", troff(1) file to print, ie 'lpr -t'
+ * v - "file name", plain raster file to print
+ *
+ * U - "file name" of datafile to unlink (ie, remove file
+ * from spool directory. To be done in a 'Pass 2',
+ * AFTER having processed all datafiles in the job).
+ *
+ */
+
+void
+ctl_freeinf(struct cjobinfo *cjinf)
+{
+#define FREESTR(xStr) \
+ if (xStr != NULL) { \
+ free(xStr); \
+ xStr = NULL;\
+ }
+
+ struct cjprivate *cpriv;
+
+ if (cjinf == NULL)
+ return;
+ cpriv = cjinf->cji_priv;
+ if ((cpriv == NULL) || (cpriv != cpriv->pub.cji_priv)) {
+ syslog(LOG_ERR, "in ctl_freeinf(%p): invalid cjinf (cpriv %p)",
+ (void *)cjinf, (void *)cpriv);
+ return;
+ }
+
+ FREESTR(cpriv->pub.cji_accthost);
+ FREESTR(cpriv->pub.cji_acctuser);
+ FREESTR(cpriv->pub.cji_class);
+ FREESTR(cpriv->pub.cji_curqueue);
+ /* [cpriv->pub.cji_fname is part of cpriv-malloced area] */
+ FREESTR(cpriv->pub.cji_jobname);
+ FREESTR(cpriv->pub.cji_mailto);
+ FREESTR(cpriv->pub.cji_headruser);
+
+ if (cpriv->cji_fstream != NULL) {
+ fclose(cpriv->cji_fstream);
+ cpriv->cji_fstream = NULL;
+ }
+
+ cjinf->cji_priv = NULL;
+ free(cpriv);
+#undef FREESTR
+}
+
+#ifdef DEBUGREADCF_FNAME
+static FILE *ctl_dbgfile = NULL;
+static struct stat ctl_dbgstat;
+#endif
+static int ctl_dbgline = 0;
+
+struct cjobinfo *
+ctl_readcf(const char *ptrname, const char *cfname)
+{
+ int id;
+ char *lbuff;
+ void *cstart;
+ FILE *cfile;
+ struct cjprivate *cpriv;
+ struct cjobinfo *cjinf;
+ size_t msize, sroom, sroom2;
+
+ cfile = fopen(cfname, "r");
+ if (cfile == NULL) {
+ syslog(LOG_ERR, "%s: ctl_readcf error fopen(%s): %s",
+ ptrname, cfname, strerror(errno));
+ return NULL;
+ }
+
+ sroom = roundup(sizeof(struct cjprivate), 8);
+ sroom2 = sroom + strlen(cfname) + 1;
+ sroom2 = roundup(sroom2, 8);
+ msize = sroom2 + CTI_LINEMAX;
+ msize = roundup(msize, 8);
+ cstart = malloc(msize);
+ if (cstart == NULL) {
+ fclose(cfile);
+ return NULL;
+ }
+ memset(cstart, 0, msize);
+ cpriv = (struct cjprivate *)cstart;
+ cpriv->pub.cji_priv = cpriv;
+
+ cpriv->pub.cji_fname = (char *)cstart + sroom;
+ strcpy(cpriv->pub.cji_fname, cfname);
+ cpriv->cji_buff = (char *)cstart + sroom2;
+ cpriv->cji_buffsize = (int)(msize - sroom2);
+ cpriv->cji_eobuff = (char *)cstart + msize - 1;
+
+ cpriv->cji_fstream = cfile;
+ cpriv->pub.cji_curqueue = strdup(ptrname);
+
+ ctl_dbgline = 0;
+#ifdef DEBUGREADCF_FNAME
+ ctl_dbgfile = NULL;
+ id = stat(DEBUGREADCF_FNAME, &ctl_dbgstat);
+ if (id != -1) {
+ /* the file exists in this spool directory, write some simple
+ * debugging info to it */
+ ctl_dbgfile = fopen(DEBUGREADCF_FNAME, "a");
+ if (ctl_dbgfile != NULL) {
+ fprintf(ctl_dbgfile, "%s: s=%p r=%ld e=%p %p->%s\n",
+ ptrname, (void *)cpriv, (long)sroom,
+ cpriv->cji_eobuff, cpriv->pub.cji_fname,
+ cpriv->pub.cji_fname);
+ }
+ }
+#endif
+ /*
+ * Copy job-attribute values from control file to the struct of
+ * "public" information. In some cases, it is invalid for the
+ * value to be a null-string, so that is ignored.
+ */
+ cjinf = &(cpriv->pub);
+ lbuff = ctl_getline(cjinf);
+ while (lbuff != NULL) {
+ id = *lbuff++;
+ switch (id) {
+ case 'C':
+ cpriv->pub.cji_class = strdup(lbuff);
+ break;
+ case 'H':
+ if (*lbuff == '\0')
+ break;
+ cpriv->pub.cji_accthost = strdup(lbuff);
+ break;
+ case 'J':
+ cpriv->pub.cji_jobname = strdup(lbuff);
+ break;
+ case 'L':
+ cpriv->pub.cji_headruser = strdup(lbuff);
+ break;
+ case 'M':
+ /*
+ * No valid mail-to address would start with a minus.
+ * If this one does, it is probably some trickster who
+ * is trying to trigger options on sendmail. Ignore.
+ */
+ if (*lbuff == '-')
+ break;
+ if (*lbuff == '\0')
+ break;
+ cpriv->pub.cji_mailto = strdup(lbuff);
+ break;
+ case 'P':
+ if (*lbuff == '\0')
+ break;
+ /* The userid must not start with a minus sign */
+ if (*lbuff == '-')
+ *lbuff = '_';
+ cpriv->pub.cji_acctuser = strdup(lbuff);
+ break;
+ default:
+ if (islower(id)) {
+ cpriv->pub.cji_dfcount++;
+ }
+ break;
+ }
+ lbuff = ctl_getline(cjinf);
+ }
+
+ /* the 'H'ost and 'P'erson fields are *always* supposed to be there */
+ if (cpriv->pub.cji_accthost == NULL)
+ cpriv->pub.cji_accthost = strdup(".na.");
+ if (cpriv->pub.cji_acctuser == NULL)
+ cpriv->pub.cji_acctuser = strdup(".na.");
+
+#ifdef DEBUGREADCF_FNAME
+ if (ctl_dbgfile != NULL) {
+ if (cpriv->cji_dumpit)
+ ctl_dumpcji(ctl_dbgfile, "end readcf", &(cpriv->pub));
+ fclose(ctl_dbgfile);
+ ctl_dbgfile = NULL;
+ }
+#endif
+ return &(cpriv->pub);
+}
+
+/*
+ * This routine renames the temporary control file as received from some
+ * other (remote) host. That file will almost always with `tfA*', because
+ * recvjob.c creates the file by changing `c' to `t' in the original name
+ * for the control file. Now if you read the RFC, you would think that all
+ * control filenames start with `cfA*'. However, it seems there are some
+ * implementations which send control filenames which start with `cf'
+ * followed by *any* letter, so this routine can not assume what the third
+ * letter will (or will not) be. Sigh.
+ *
+ * So this will rewrite the temporary file to `rf*' (correcting any lines
+ * which need correcting), rename that `rf*' file to `cf*', and then remove
+ * the original `tf*' temporary file.
+ *
+ * The *main* purpose of this routine is to be paranoid about the contents
+ * of that control file. It is partially meant to protect against people
+ * TRYING to cause trouble (perhaps after breaking into root of some host
+ * that this host will accept print jobs from). The fact that we're willing
+ * to print jobs from some remote host does not mean that we should blindly
+ * do anything that host tells us to do.
+ *
+ * This is also meant to protect us from errors in other implementations of
+ * lpr, particularly since we may want to use some values from the control
+ * file as environment variables when it comes time to print, or as parameters
+ * to commands which will be exec'ed, or values in statistics records.
+ *
+ * This may also do some "conversions" between how different versions of
+ * lpr or lprNG define the contents of various lines in a control file.
+ *
+ * If there is an error, it returns a pointer to a descriptive error message.
+ * Error messages which are RETURNED (as opposed to syslog-ed) do not include
+ * the printer-queue name. Let the caller add that if it is wanted.
+ */
+char *
+ctl_renametf(const char *ptrname, const char *tfname)
+{
+ int chk3rd, has_uc, newfd, nogood, res;
+ FILE *newcf;
+ struct cjobinfo *cjinf;
+ char *lbuff, *slash, *cp;
+ char tfname2[NAME_MAX+1], cfname2[NAME_MAX+1];
+ char errm[CTI_LINEMAX];
+
+#ifdef TRIGGERTEST_FNAME
+ struct stat tstat;
+ res = stat(TRIGGERTEST_FNAME, &tstat);
+ if (res == -1) {
+ /*
+ * if the trigger file does NOT exist in this spool directory,
+ * then do the exact same steps that the pre-ctlinfo code had
+ * been doing. Ie, very little.
+ */
+ strlcpy(cfname2, tfname, sizeof(cfname2));
+ cfname2[0] = 'c';
+ res = link(tfname, cfname2);
+ if (res < 0) {
+ snprintf(errm, sizeof(errm),
+ "ctl_renametf error link(%s,%s): %s", tfname,
+ cfname2, strerror(errno));
+ return strdup(errm);
+ }
+ unlink(tfname);
+ return NULL;
+ }
+#endif
+ cjinf = NULL; /* in case of early jump to error_ret */
+ newcf = NULL; /* in case of early jump to error_ret */
+ *errm = '\0'; /* in case of early jump to error_ret */
+
+ chk3rd = tfname[2];
+ if ((tfname[0] != 't') || (tfname[1] != 'f') || (!isalpha(chk3rd))) {
+ snprintf(errm, sizeof(errm),
+ "ctl_renametf invalid filename: %s", tfname);
+ goto error_ret;
+ }
+
+ cjinf = ctl_readcf(ptrname, tfname);
+ if (cjinf == NULL) {
+ snprintf(errm, sizeof(errm),
+ "ctl_renametf error cti_readcf(%s)", tfname);
+ goto error_ret;
+ }
+
+ /*
+ * This uses open+fdopen instead of fopen because that combination
+ * gives us greater control over file-creation issues.
+ */
+ strlcpy(tfname2, tfname, sizeof(tfname2));
+ tfname2[0] = 'r'; /* rf<letter><job><hostname> */
+ newfd = open(tfname2, O_WRONLY|O_CREAT|O_TRUNC, 0660);
+ if (newfd == -1) {
+ snprintf(errm, sizeof(errm),
+ "ctl_renametf error open(%s): %s", tfname2,
+ strerror(errno));
+ goto error_ret;
+ }
+ newcf = fdopen(newfd, "w");
+ if (newcf == NULL) {
+ close(newfd);
+ snprintf(errm, sizeof(errm),
+ "ctl_renametf error fopen(%s): %s", tfname2,
+ strerror(errno));
+ goto error_ret;
+ }
+
+ /*
+ * Do extra sanity checks on some key job-attribute fields, and
+ * write them out first (thus making sure they are written in the
+ * order we generally expect them to be in).
+ */
+ /*
+ * Some lpr implementations on PC's set a null-string for their
+ * hostname. A MacOS 10 system which has not correctly setup
+ * /etc/hostconfig will claim a hostname of 'localhost'. Anything
+ * with blanks in it would be an invalid value for hostname. For
+ * any of these invalid hostname values, replace the given value
+ * with the name of the host that this job is coming from.
+ */
+ nogood = 0;
+ if (cjinf->cji_accthost == NULL)
+ nogood = 1;
+ else if (strcmp(cjinf->cji_accthost, ".na.") == 0)
+ nogood = 1;
+ else if (strcmp(cjinf->cji_accthost, "localhost") == 0)
+ nogood = 1;
+ else {
+ for (cp = cjinf->cji_accthost; *cp != '\0'; cp++) {
+ if (*cp <= ' ') {
+ nogood = 1;
+ break;
+ }
+ }
+ }
+ if (nogood)
+ fprintf(newcf, "H%s\n", from_host);
+ else
+ fprintf(newcf, "H%s\n", cjinf->cji_accthost);
+
+ /*
+ * Now do some sanity checks on the 'P' (original userid) value. Note
+ * that the 'P'erson line is the second line which is ALWAYS supposed
+ * to be present in a control file.
+ *
+ * There is no particularly good value to use for replacements, but
+ * at least make sure the value is something reasonable to use in
+ * environment variables and statistics records. Again, some PC
+ * implementations send a null-string for a value. Various Mac
+ * implementations will set whatever string the user has set for
+ * their 'Owner Name', which usually includes blanks, etc.
+ */
+ nogood = 0;
+ if (cjinf->cji_acctuser == NULL)
+ nogood = 1;
+ else if (strcmp(cjinf->cji_acctuser, ".na.") == 0)
+ ; /* No further checks needed... */
+ else {
+ has_uc = 0;
+ cp = cjinf->cji_acctuser;
+ if (*cp == '-')
+ *cp++ = '_';
+ for (; *cp != '\0'; cp++) {
+ if (islowerch(*cp) || isdigitch(*cp))
+ continue; /* Standard valid characters */
+ if (strchr(OTHER_USERID_CHARS, *cp) != NULL)
+ continue; /* Some more valid characters */
+ if (isupperch(*cp)) {
+ has_uc = 1; /* These may be valid... */
+ continue;
+ }
+ *cp = '_';
+ }
+ /*
+ * Some Windows hosts send print jobs where the correct userid
+ * has been converted to uppercase, and that can cause trouble
+ * for sites that expect the correct value (for something like
+ * accounting). On the other hand, some sites do use uppercase
+ * in their userids, so we can't blindly convert to lowercase.
+ */
+ if (has_uc && (getpwnam(cjinf->cji_acctuser) == NULL)) {
+ for (cp = cjinf->cji_acctuser; *cp != '\0'; cp++) {
+ if (isupperch(*cp))
+ *cp = tolowerch(*cp);
+ }
+ }
+ }
+ if (nogood)
+ fprintf(newcf, "P%s\n", ".na.");
+ else
+ fprintf(newcf, "P%s\n", cjinf->cji_acctuser);
+
+ /* No need for sanity checks on class, jobname, "literal" user. */
+ if (cjinf->cji_class != NULL)
+ fprintf(newcf, "C%s\n", cjinf->cji_class);
+ if (cjinf->cji_jobname != NULL)
+ fprintf(newcf, "J%s\n", cjinf->cji_jobname);
+ if (cjinf->cji_headruser != NULL)
+ fprintf(newcf, "L%s\n", cjinf->cji_headruser);
+
+ /*
+ * This should probably add more sanity checks on mailto value.
+ * Note that if the mailto value is "wrong", then there's no good
+ * way to know what the "correct" value would be, and we should not
+ * semd email to some random address. At least for now, just ignore
+ * any invalid values.
+ */
+ nogood = 0;
+ if (cjinf->cji_mailto == NULL)
+ nogood = 1;
+ else {
+ for (cp = cjinf->cji_mailto; *cp != '\0'; cp++) {
+ if (*cp <= ' ') {
+ nogood = 1;
+ break;
+ }
+ }
+ }
+ if (!nogood)
+ fprintf(newcf, "M%s\n", cjinf->cji_mailto);
+
+ /*
+ * Now go thru the old control file, copying all information which
+ * hasn't already been written into the new file.
+ */
+ ctl_rewindcf(cjinf);
+ lbuff = ctl_getline(cjinf);
+ while (lbuff != NULL) {
+ switch (lbuff[0]) {
+ case 'H':
+ case 'P':
+ case 'C':
+ case 'J':
+ case 'L':
+ case 'M':
+ /* already wrote values for these to the newcf */
+ break;
+ case 'N':
+ /* see comments under 'U'... */
+ if (cjinf->cji_dfcount == 0) {
+ /* in this case, 'N's will be done in 'U' */
+ break;
+ }
+ fprintf(newcf, "%s\n", lbuff);
+ break;
+ case 'U':
+ /*
+ * check for the very common case where the remote
+ * host had to process 'lpr -s -r', but it did not
+ * remove the Unlink line from the control file.
+ * Such Unlink lines will legitimately have a '/' in
+ * them, but it is the original lpr host which would
+ * have done the unlink of such files, and not any
+ * host receiving that job.
+ */
+ slash = strchr(lbuff, '/');
+ if (slash != NULL) {
+ break; /* skip this line */
+ }
+ /*
+ * Okay, another kind of broken lpr implementation
+ * is one which send datafiles, and Unlink's those
+ * datafiles, but never includes any PRINT request
+ * for those files. Experimentation shows that one
+ * copy of those datafiles should be printed with a
+ * format of 'f'. If this is an example of such a
+ * screwed-up control file, fix it here.
+ */
+ if (cjinf->cji_dfcount == 0) {
+ lbuff++;
+ if (strncmp(lbuff, "df", (size_t)2) == 0) {
+ fprintf(newcf, "f%s\n", lbuff);
+ fprintf(newcf, "U%s\n", lbuff);
+ fprintf(newcf, "N%s\n", lbuff);
+ }
+ break;
+ }
+ fprintf(newcf, "%s\n", lbuff);
+ break;
+ default:
+ fprintf(newcf, "%s\n", lbuff);
+ break;
+ }
+ lbuff = ctl_getline(cjinf);
+ }
+
+ ctl_freeinf(cjinf);
+ cjinf = NULL;
+
+ res = fclose(newcf);
+ newcf = NULL;
+ if (res != 0) {
+ snprintf(errm, sizeof(errm),
+ "ctl_renametf error fclose(%s): %s", tfname2,
+ strerror(errno));
+ goto error_ret;
+ }
+
+ strlcpy(cfname2, tfname, sizeof(cfname2));
+ cfname2[0] = 'c'; /* rename new file to 'cfA*' */
+ res = link(tfname2, cfname2);
+ if (res != 0) {
+ snprintf(errm, sizeof(errm),
+ "ctl_renametf error link(%s,%s): %s", tfname2, cfname2,
+ strerror(errno));
+ goto error_ret;
+ }
+
+ /* All the important work is done. Now just remove temp files */
+#ifdef LEAVE_TMPCF_FILES
+ {
+ struct stat tfstat;
+ size_t size1;
+ tfstat.st_size = 1; /* certainly invalid value */
+ res = stat(tfname, &tfstat);
+ size1 = tfstat.st_size;
+ tfstat.st_size = 2; /* certainly invalid value */
+ res = stat(tfname2, &tfstat);
+ /*
+ * If the sizes do not match, or either stat call failed,
+ * then do not remove the temp files, but just move them
+ * out of the way. This is so I can see what this routine
+ * had changed (and the files won't interfere with some
+ * later job coming in from the same host). In this case,
+ * we don't care if we clobber some previous file.
+ */
+ if (size1 != tfstat.st_size) {
+ strlcpy(cfname2, tfname, sizeof(cfname2));
+ strlcat(cfname2, "._T", sizeof(cfname2));
+ rename(tfname, cfname2);
+ strlcpy(cfname2, tfname2, sizeof(cfname2));
+ strlcat(cfname2, "._T", sizeof(cfname2));
+ rename(tfname2, cfname2);
+ return NULL;
+ }
+ }
+#endif
+ unlink(tfname);
+ unlink(tfname2);
+
+ return NULL;
+
+error_ret:
+ if (cjinf != NULL)
+ ctl_freeinf(cjinf);
+ if (newcf != NULL)
+ fclose(newcf);
+
+ if (*errm != '\0')
+ return strdup(errm);
+ return strdup("ctl_renametf internal (missed) error");
+}
+
+void
+ctl_rewindcf(struct cjobinfo *cjinf)
+{
+ struct cjprivate *cpriv;
+
+ if (cjinf == NULL)
+ return;
+ cpriv = cjinf->cji_priv;
+ if ((cpriv == NULL) || (cpriv != cpriv->pub.cji_priv)) {
+ syslog(LOG_ERR, "in ctl_rewindcf(%p): invalid cjinf (cpriv %p)",
+ (void *)cjinf, (void *)cpriv);
+ return;
+ }
+
+ rewind(cpriv->cji_fstream); /* assume no errors... :-) */
+}
+
+char *
+ctl_rmjob(const char *ptrname, const char *cfname)
+{
+ struct cjobinfo *cjinf;
+ char *lbuff;
+ char errm[CTI_LINEMAX];
+
+ cjinf = ctl_readcf(ptrname, cfname);
+ if (cjinf == NULL) {
+ snprintf(errm, sizeof(errm),
+ "ctl_renametf error cti_readcf(%s)", cfname);
+ return strdup(errm);
+ }
+
+ ctl_rewindcf(cjinf);
+ lbuff = ctl_getline(cjinf);
+ while (lbuff != NULL) {
+ /* obviously we need to fill in the following... */
+ switch (lbuff[0]) {
+ case 'S':
+ break;
+ case 'U':
+ break;
+ default:
+ break;
+ }
+ lbuff = ctl_getline(cjinf);
+ }
+
+ ctl_freeinf(cjinf);
+ cjinf = NULL;
+
+ return NULL;
+}
+
+/*
+ * The following routine was originally written to pin down a bug. It is
+ * no longer needed for that problem, but may be useful to keep around for
+ * other debugging.
+ */
+void
+ctl_dumpcji(FILE *dbg_stream, const char *heading, struct cjobinfo *cjinf)
+{
+#define PRINTSTR(xHdr,xStr) \
+ astr = xStr; \
+ ctl_dbgline++; \
+ fprintf(dbg_stream, "%4d] %12s = ", ctl_dbgline, xHdr); \
+ if (astr == NULL) \
+ fprintf(dbg_stream, "NULL\n"); \
+ else \
+ fprintf(dbg_stream, "%p -> %s\n", astr, astr)
+
+ struct cjprivate *cpriv;
+ char *astr;
+
+ if (cjinf == NULL) {
+ fprintf(dbg_stream,
+ "ctl_dumpcji: ptr to cjobinfo for '%s' is NULL\n",
+ heading);
+ return;
+ }
+ cpriv = cjinf->cji_priv;
+
+ fprintf(dbg_stream, "ctl_dumpcji: Dump '%s' of cjobinfo at %p->%p\n",
+ heading, (void *)cjinf, cpriv->cji_buff);
+
+ PRINTSTR("accthost.H", cpriv->pub.cji_accthost);
+ PRINTSTR("acctuser.P", cpriv->pub.cji_acctuser);
+ PRINTSTR("class.C", cpriv->pub.cji_class);
+ PRINTSTR("cf-qname", cpriv->pub.cji_curqueue);
+ PRINTSTR("cf-fname", cpriv->pub.cji_fname);
+ PRINTSTR("jobname.J", cpriv->pub.cji_jobname);
+ PRINTSTR("mailto.M", cpriv->pub.cji_mailto);
+ PRINTSTR("headruser.L", cpriv->pub.cji_headruser);
+
+ ctl_dbgline++;
+ fprintf(dbg_stream, "%4d] %12s = ", ctl_dbgline, "*cjprivate");
+ if (cpriv->pub.cji_priv == NULL)
+ fprintf(dbg_stream, "NULL !!\n");
+ else
+ fprintf(dbg_stream, "%p\n", (void *)cpriv->pub.cji_priv);
+
+ fprintf(dbg_stream, "|- - - - --> Dump '%s' complete\n", heading);
+
+ /* flush output for the benefit of anyone doing a 'tail -f' */
+ fflush(dbg_stream);
+
+#undef PRINTSTR
+}
+
+/*
+ * This routine reads in the next line from the control-file, and removes
+ * the trailing newline character.
+ *
+ * Historical note: Earlier versions of this routine did tab-expansion for
+ * ALL lines read in, which did not make any sense for most of the lines
+ * in a control file. For the lines where tab-expansion is useful, it will
+ * now have to be done by the calling routine.
+ */
+static char *
+ctl_getline(struct cjobinfo *cjinf)
+{
+ char *strp, *nl;
+ struct cjprivate *cpriv;
+
+ if (cjinf == NULL)
+ return NULL;
+ cpriv = cjinf->cji_priv;
+ if ((cpriv == NULL) || (cpriv != cpriv->pub.cji_priv)) {
+ syslog(LOG_ERR, "in ctl_getline(%p): invalid cjinf (cpriv %p)",
+ (void *)cjinf, (void *)cpriv);
+ return NULL;
+ }
+
+ errno = 0;
+ strp = fgets(cpriv->cji_buff, cpriv->cji_buffsize, cpriv->cji_fstream);
+ if (strp == NULL) {
+ if (errno != 0)
+ syslog(LOG_ERR, "%s: ctl_getline error fgets(%s): %s",
+ cpriv->pub.cji_curqueue, cpriv->pub.cji_fname,
+ strerror(errno));
+ return NULL;
+ }
+ nl = strchr(strp, '\n');
+ if (nl != NULL)
+ *nl = '\0';
+
+#ifdef DEBUGREADCF_FNAME
+ /* I'd like to find out if the previous work to expand tabs was ever
+ * really used, and if so, on what lines and for what reason.
+ * Yes, all this work probably means I'm obsessed about this 'tab'
+ * issue, but isn't programming a matter of obsession?
+ */
+ {
+ int tabcnt;
+ char *ch;
+
+ tabcnt = 0;
+ ch = strp;
+ for (ch = strp; *ch != '\0'; ch++) {
+ if (*ch == '\t')
+ tabcnt++;
+ }
+
+ if (tabcnt && (ctl_dbgfile != NULL)) {
+ cpriv->cji_dumpit++;
+ fprintf(ctl_dbgfile, "%s: tabs=%d '%s'\n",
+ cpriv->pub.cji_fname, tabcnt, cpriv->cji_buff);
+ }
+ }
+#endif
+ return strp;
+}
diff --git a/usr.sbin/lpr/common_source/ctlinfo.h b/usr.sbin/lpr/common_source/ctlinfo.h
new file mode 100644
index 000000000000..82d0c92df458
--- /dev/null
+++ b/usr.sbin/lpr/common_source/ctlinfo.h
@@ -0,0 +1,74 @@
+/*-
+ * SPDX-License-Identifier: BSD-2-Clause
+ *
+ * ------+---------+---------+---------+---------+---------+---------+---------*
+ * Copyright (c) 2001,2011 - Garance Alistair Drosehn <gad@FreeBSD.org>.
+ * All rights reserved.
+ *
+ * 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.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR 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 AUTHOR 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.
+ *
+ * The views and conclusions contained in the software and documentation
+ * are those of the authors and should not be interpreted as representing
+ * official policies, either expressed or implied, of the FreeBSD Project.
+ *
+ * ------+---------+---------+---------+---------+---------+---------+---------*
+ * ------+---------+---------+---------+---------+---------+---------+---------*
+ */
+
+/*
+ * ctlinfo - This collection of routines will know everything there is to
+ * know about the information inside a control file ('cf*') which is used
+ * to describe a print job in lpr & friends. The eventual goal is that it
+ * will be the ONLY source file to know what's inside these control-files.
+ */
+
+struct cjprivate; /* used internal to ctl* routines */
+
+struct cjobinfo {
+ int cji_dfcount; /* number of data files to print */
+ int cji_uncount; /* number of unlink-file requests */
+ char *cji_accthost; /* the host that this job came from,
+ * for accounting purposes (usually
+ * the host where the original 'lpr'
+ * was done) */
+ char *cji_acctuser; /* userid who should be charged for
+ * this job (usually, the userid which
+ * did the original 'lpr') */
+ char *cji_class; /* class-name */
+ char *cji_curqueue; /* printer-queue that this cf-file is
+ * currently sitting in (mainly used
+ * in syslog error messages) */
+ char *cji_fname; /* filename of the control file */
+ char *cji_jobname; /* job-name (for banner) */
+ char *cji_mailto; /* userid to send email to (or null) */
+ char *cji_headruser; /* "literal" user-name (for banner) or
+ * NULL if no banner-page is wanted */
+ struct cjprivate *cji_priv;
+};
+
+#include "lp.cdefs.h" /* A cross-platform version of <sys/cdefs.h> */
+
+__BEGIN_DECLS
+void ctl_freeinf(struct cjobinfo *_cjinf);
+struct cjobinfo *ctl_readcf(const char *_ptrname, const char *_cfname);
+char *ctl_renametf(const char *_ptrname, const char *_tfname);
+__END_DECLS
diff --git a/usr.sbin/lpr/common_source/displayq.c b/usr.sbin/lpr/common_source/displayq.c
new file mode 100644
index 000000000000..e9ef544f0399
--- /dev/null
+++ b/usr.sbin/lpr/common_source/displayq.c
@@ -0,0 +1,630 @@
+/*-
+ * SPDX-License-Identifier: BSD-3-Clause
+ *
+ * Copyright (c) 1983, 1993
+ * The Regents of the University of California. All rights reserved.
+ *
+ * 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 "lp.cdefs.h" /* A cross-platform version of <sys/cdefs.h> */
+#include <sys/param.h>
+#include <sys/stat.h>
+
+#include <ctype.h>
+#include <dirent.h>
+#include <err.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <signal.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#define psignal foil_gcc_psignal
+#define sys_siglist foil_gcc_siglist
+#include <unistd.h>
+#undef psignal
+#undef sys_siglist
+
+#include "lp.h"
+#include "lp.local.h"
+#include "pathnames.h"
+
+/*
+ * Routines to display the state of the queue.
+ */
+#define JOBCOL 40 /* column for job # in -l format */
+#define OWNCOL 7 /* start of Owner column in normal */
+#define SIZCOL 62 /* start of Size column in normal */
+
+/*
+ * isprint() takes a parameter of 'int', but expect values in the range
+ * of unsigned char. Define a wrapper which takes a value of type 'char',
+ * whether signed or unsigned, and ensure it ends up in the right range.
+ */
+#define isprintch(Anychar) isprint((u_char)(Anychar))
+
+/*
+ * Stuff for handling job specifications
+ */
+static int col; /* column on screen */
+static char current[MAXNAMLEN+1]; /* current file being printed */
+static char file[MAXNAMLEN+1]; /* print file name */
+static int first; /* first file in ``files'' column? */
+static int garbage; /* # of garbage cf files */
+static int lflag; /* long output option */
+static int rank; /* order to be printed (-1=none, 0=active) */
+static long totsize; /* total print job size in bytes */
+
+static const char *head0 = "Rank Owner Job Files";
+static const char *head1 = "Total Size\n";
+
+static void alarmhandler(int _signo);
+static void filtered_write(char *_obuffer, int _wlen, FILE *_wstream);
+static void daemonwarn(const struct printer *_pp);
+
+/*
+ * Display the current state of the queue. Format = 1 if long format.
+ */
+void
+displayq(struct printer *pp, int format)
+{
+ register struct jobqueue *q;
+ register int i, nitems, fd, ret;
+ char *cp, *endp;
+ struct jobqueue **queue;
+ struct stat statb;
+ FILE *fp;
+ void (*savealrm)(int);
+
+ lflag = format;
+ totsize = 0;
+ rank = -1;
+
+ if ((cp = checkremote(pp))) {
+ printf("Warning: %s\n", cp);
+ free(cp);
+ }
+
+ /*
+ * Print out local queue
+ * Find all the control files in the spooling directory
+ */
+ PRIV_START
+ if (chdir(pp->spool_dir) < 0)
+ fatal(pp, "cannot chdir to spooling directory: %s",
+ strerror(errno));
+ PRIV_END
+ if ((nitems = getq(pp, &queue)) < 0)
+ fatal(pp, "cannot examine spooling area\n");
+ PRIV_START
+ ret = stat(pp->lock_file, &statb);
+ PRIV_END
+ if (ret >= 0) {
+ if (statb.st_mode & LFM_PRINT_DIS) {
+ if (pp->remote)
+ printf("%s: ", local_host);
+ printf("Warning: %s is down: ", pp->printer);
+ PRIV_START
+ fd = open(pp->status_file, O_RDONLY|O_SHLOCK);
+ PRIV_END
+ if (fd >= 0) {
+ while ((i = read(fd, line, sizeof(line))) > 0)
+ (void) fwrite(line, 1, i, stdout);
+ (void) close(fd); /* unlocks as well */
+ } else
+ putchar('\n');
+ }
+ if (statb.st_mode & LFM_QUEUE_DIS) {
+ if (pp->remote)
+ printf("%s: ", local_host);
+ printf("Warning: %s queue is turned off\n",
+ pp->printer);
+ }
+ }
+
+ if (nitems) {
+ PRIV_START
+ fp = fopen(pp->lock_file, "r");
+ PRIV_END
+ if (fp == NULL)
+ daemonwarn(pp);
+ else {
+ /* get daemon pid */
+ cp = current;
+ endp = cp + sizeof(current) - 1;
+ while ((i = getc(fp)) != EOF && i != '\n') {
+ if (cp < endp)
+ *cp++ = i;
+ }
+ *cp = '\0';
+ i = atoi(current);
+ if (i <= 0) {
+ ret = -1;
+ } else {
+ PRIV_START
+ ret = kill(i, 0);
+ PRIV_END
+ }
+ if (ret < 0) {
+ daemonwarn(pp);
+ } else {
+ /* read current file name */
+ cp = current;
+ endp = cp + sizeof(current) - 1;
+ while ((i = getc(fp)) != EOF && i != '\n') {
+ if (cp < endp)
+ *cp++ = i;
+ }
+ *cp = '\0';
+ /*
+ * Print the status file.
+ */
+ if (pp->remote)
+ printf("%s: ", local_host);
+ PRIV_START
+ fd = open(pp->status_file, O_RDONLY|O_SHLOCK);
+ PRIV_END
+ if (fd >= 0) {
+ while ((i = read(fd, line,
+ sizeof(line))) > 0)
+ fwrite(line, 1, i, stdout);
+ close(fd); /* unlocks as well */
+ } else
+ putchar('\n');
+ }
+ (void) fclose(fp);
+ }
+ /*
+ * Now, examine the control files and print out the jobs to
+ * be done for each user.
+ */
+ if (!lflag)
+ header();
+ for (i = 0; i < nitems; i++) {
+ q = queue[i];
+ inform(pp, q->job_cfname);
+ free(q);
+ }
+ free(queue);
+ }
+ if (!pp->remote) {
+ if (nitems == 0)
+ puts("no entries");
+ return;
+ }
+
+ /*
+ * Print foreign queue
+ * Note that a file in transit may show up in either queue.
+ */
+ if (nitems)
+ putchar('\n');
+ (void) snprintf(line, sizeof(line), "%c%s", format ? '\4' : '\3',
+ pp->remote_queue);
+ cp = line;
+ for (i = 0; i < requests && cp-line+10 < sizeof(line) - 1; i++) {
+ cp += strlen(cp);
+ (void) sprintf(cp, " %d", requ[i]);
+ }
+ for (i = 0; i < users && cp - line + 1 + strlen(user[i]) <
+ sizeof(line) - 1; i++) {
+ cp += strlen(cp);
+ *cp++ = ' ';
+ (void) strcpy(cp, user[i]);
+ }
+ strcat(line, "\n");
+ savealrm = signal(SIGALRM, alarmhandler);
+ alarm(pp->conn_timeout);
+ fd = getport(pp, pp->remote_host, 0);
+ alarm(0);
+ (void)signal(SIGALRM, savealrm);
+ if (fd < 0) {
+ if (from_host != local_host)
+ printf("%s: ", local_host);
+ printf("connection to %s is down\n", pp->remote_host);
+ }
+ else {
+ i = strlen(line);
+ if (write(fd, line, i) != i)
+ fatal(pp, "Lost connection");
+ while ((i = read(fd, line, sizeof(line))) > 0)
+ filtered_write(line, i, stdout);
+ filtered_write(NULL, -1, stdout);
+ (void) close(fd);
+ }
+}
+
+/*
+ * The lpq-info read from remote hosts may contain unprintable characters,
+ * or carriage-returns instead of line-feeds. Clean those up before echoing
+ * the lpq-info line(s) to stdout. The info may also be missing any kind of
+ * end-of-line character. This also turns CRLF and LFCR into a plain LF.
+ *
+ * This routine may be called multiple times to process a single set of
+ * information, and after a set is finished this routine must be called
+ * one extra time with NULL specified as the buffer address.
+ */
+static void
+filtered_write(char *wbuffer, int wlen, FILE *wstream)
+{
+ static char lastchar, savedchar;
+ char *chkptr, *dest_end, *dest_ch, *nxtptr, *w_end;
+ int destlen;
+ char destbuf[BUFSIZ];
+
+ if (wbuffer == NULL) {
+ if (savedchar != '\0') {
+ if (savedchar == '\r')
+ savedchar = '\n';
+ fputc(savedchar, wstream);
+ lastchar = savedchar;
+ savedchar = '\0';
+ }
+ if (lastchar != '\0' && lastchar != '\n')
+ fputc('\n', wstream);
+ lastchar = '\0';
+ return;
+ }
+
+ dest_ch = &destbuf[0];
+ dest_end = dest_ch + sizeof(destbuf);
+ chkptr = wbuffer;
+ w_end = wbuffer + wlen;
+ lastchar = '\0';
+ if (savedchar != '\0') {
+ chkptr = &savedchar;
+ nxtptr = wbuffer;
+ } else
+ nxtptr = chkptr + 1;
+
+ while (chkptr < w_end) {
+ if (nxtptr < w_end) {
+ if ((*chkptr == '\r' && *nxtptr == '\n') ||
+ (*chkptr == '\n' && *nxtptr == '\r')) {
+ *dest_ch++ = '\n';
+ /* want to skip past that second character */
+ nxtptr++;
+ goto check_next;
+ }
+ } else {
+ /* This is the last byte in the buffer given on this
+ * call, so check if it could be the first-byte of a
+ * significant two-byte sequence. If it is, then
+ * don't write it out now, but save for checking in
+ * the next call.
+ */
+ savedchar = '\0';
+ if (*chkptr == '\r' || *chkptr == '\n') {
+ savedchar = *chkptr;
+ break;
+ }
+ }
+ if (*chkptr == '\r')
+ *dest_ch++ = '\n';
+#if 0 /* XXX - don't translate unprintable characters (yet) */
+ else if (*chkptr != '\t' && *chkptr != '\n' &&
+ !isprintch(*chkptr))
+ *dest_ch++ = '?';
+#endif
+ else
+ *dest_ch++ = *chkptr;
+
+check_next:
+ chkptr = nxtptr;
+ nxtptr = chkptr + 1;
+ if (dest_ch >= dest_end) {
+ destlen = dest_ch - &destbuf[0];
+ fwrite(destbuf, 1, destlen, wstream);
+ lastchar = destbuf[destlen - 1];
+ dest_ch = &destbuf[0];
+ }
+ }
+ destlen = dest_ch - &destbuf[0];
+ if (destlen > 0) {
+ fwrite(destbuf, 1, destlen, wstream);
+ lastchar = destbuf[destlen - 1];
+ }
+}
+
+/*
+ * Print a warning message if there is no daemon present.
+ */
+static void
+daemonwarn(const struct printer *pp)
+{
+ if (pp->remote)
+ printf("%s: ", local_host);
+ puts("Warning: no daemon present");
+ current[0] = '\0';
+}
+
+/*
+ * Print the header for the short listing format
+ */
+void
+header(void)
+{
+ printf("%s", head0);
+ col = strlen(head0)+1;
+ blankfill(SIZCOL);
+ printf("%s", head1);
+}
+
+void
+inform(const struct printer *pp, char *cf)
+{
+ int copycnt, jnum;
+ char savedname[MAXPATHLEN+1];
+ FILE *cfp;
+
+ /*
+ * There's a chance the control file has gone away
+ * in the meantime; if this is the case just keep going
+ */
+ PRIV_START
+ if ((cfp = fopen(cf, "r")) == NULL)
+ return;
+ PRIV_END
+
+ if (rank < 0)
+ rank = 0;
+ if (pp->remote || garbage || strcmp(cf, current))
+ rank++;
+
+ /*
+ * The cf-file may include commands to print more than one datafile
+ * from the user. For each datafile, the cf-file contains at least
+ * one line which starts with some format-specifier ('a'-'z'), and
+ * a second line ('N'ame) which indicates the original name the user
+ * specified for that file. There can be multiple format-spec lines
+ * for a single Name-line, if the user requested multiple copies of
+ * that file. Standard lpr puts the format-spec line(s) before the
+ * Name-line, while lprNG puts the Name-line before the format-spec
+ * line(s). This section needs to handle the lines in either order.
+ */
+ copycnt = 0;
+ file[0] = '\0';
+ savedname[0] = '\0';
+ jnum = calc_jobnum(cf, NULL);
+ while (get_line(cfp)) {
+ switch (line[0]) {
+ case 'P': /* Was this file specified in the user's list? */
+ if (!inlist(line+1, cf)) {
+ fclose(cfp);
+ return;
+ }
+ if (lflag) {
+ printf("\n%s: ", line+1);
+ col = strlen(line+1) + 2;
+ prank(rank);
+ blankfill(JOBCOL);
+ printf(" [job %s]\n", cf+3);
+ } else {
+ col = 0;
+ prank(rank);
+ blankfill(OWNCOL);
+ printf("%-10s %-3d ", line+1, jnum);
+ col += 16;
+ first = 1;
+ }
+ continue;
+ default: /* some format specifer and file name? */
+ if (line[0] < 'a' || line[0] > 'z')
+ break;
+ if (copycnt == 0 || strcmp(file, line+1) != 0) {
+ strlcpy(file, line + 1, sizeof(file));
+ }
+ copycnt++;
+ /*
+ * deliberately 'continue' to another get_line(), so
+ * all format-spec lines for this datafile are read
+ * in and counted before calling show()
+ */
+ continue;
+ case 'N':
+ strlcpy(savedname, line + 1, sizeof(savedname));
+ break;
+ }
+ if ((file[0] != '\0') && (savedname[0] != '\0')) {
+ show(savedname, file, copycnt);
+ copycnt = 0;
+ file[0] = '\0';
+ savedname[0] = '\0';
+ }
+ }
+ fclose(cfp);
+ /* check for a file which hasn't been shown yet */
+ if (file[0] != '\0') {
+ if (savedname[0] == '\0') {
+ /* a safeguard in case the N-ame line is missing */
+ strlcpy(savedname, file, sizeof(savedname));
+ }
+ show(savedname, file, copycnt);
+ }
+ if (!lflag) {
+ blankfill(SIZCOL);
+ printf("%ld bytes\n", totsize);
+ totsize = 0;
+ }
+}
+
+int
+inlist(char *uname, char *cfile)
+{
+ int *r, jnum;
+ char **u;
+ const char *cfhost;
+
+ if (users == 0 && requests == 0)
+ return(1);
+ /*
+ * Check to see if it's in the user list
+ */
+ for (u = user; u < &user[users]; u++)
+ if (!strcmp(*u, uname))
+ return(1);
+ /*
+ * Check the request list
+ */
+ jnum = calc_jobnum(cfile, &cfhost);
+ for (r = requ; r < &requ[requests]; r++)
+ if (*r == jnum && !strcmp(cfhost, from_host))
+ return(1);
+ return(0);
+}
+
+void
+show(const char *nfile, const char *datafile, int copies)
+{
+ if (strcmp(nfile, " ") == 0)
+ nfile = "(standard input)";
+ if (lflag)
+ ldump(nfile, datafile, copies);
+ else
+ dump(nfile, datafile, copies);
+}
+
+/*
+ * Fill the line with blanks to the specified column
+ */
+void
+blankfill(int tocol)
+{
+ while (col++ < tocol)
+ putchar(' ');
+}
+
+/*
+ * Give the abbreviated dump of the file names
+ */
+void
+dump(const char *nfile, const char *datafile, int copies)
+{
+ struct stat lbuf;
+ const char etctmpl[] = ", ...";
+ char etc[sizeof(etctmpl)];
+ char *lastsep;
+ short fill, nlen;
+ short rem, remetc;
+
+ /*
+ * Print as many filenames as will fit
+ * (leaving room for the 'total size' field)
+ */
+ fill = first ? 0 : 2; /* fill space for ``, '' */
+ nlen = strlen(nfile);
+ rem = SIZCOL - 1 - col;
+ if (nlen + fill > rem) {
+ if (first) {
+ /* print the right-most part of the name */
+ printf("...%s ", &nfile[3+nlen-rem]);
+ col = SIZCOL;
+ } else if (rem > 0) {
+ /* fit as much of the etc-string as we can */
+ remetc = rem;
+ if (rem > strlen(etctmpl))
+ remetc = strlen(etctmpl);
+ etc[0] = '\0';
+ strncat(etc, etctmpl, remetc);
+ printf("%s", etc);
+ col += remetc;
+ rem -= remetc;
+ /* room for the last segment of this filename? */
+ lastsep = strrchr(nfile, '/');
+ if ((lastsep != NULL) && (rem > strlen(lastsep))) {
+ /* print the right-most part of this name */
+ printf("%s", lastsep);
+ col += strlen(lastsep);
+ } else {
+ /* do not pack any more names in here */
+ blankfill(SIZCOL);
+ }
+ }
+ } else {
+ if (!first)
+ printf(", ");
+ printf("%s", nfile);
+ col += nlen + fill;
+ }
+ first = 0;
+
+ PRIV_START
+ if (*datafile && !stat(datafile, &lbuf))
+ totsize += copies * lbuf.st_size;
+ PRIV_END
+}
+
+/*
+ * Print the long info about the file
+ */
+void
+ldump(const char *nfile, const char *datafile, int copies)
+{
+ struct stat lbuf;
+
+ putchar('\t');
+ if (copies > 1)
+ printf("%-2d copies of %-19s", copies, nfile);
+ else
+ printf("%-32s", nfile);
+ if (*datafile && !stat(datafile, &lbuf))
+ printf(" %qd bytes", (long long) lbuf.st_size);
+ else
+ printf(" ??? bytes");
+ putchar('\n');
+}
+
+/*
+ * Print the job's rank in the queue,
+ * update col for screen management
+ */
+void
+prank(int n)
+{
+ char rline[100];
+ static const char *r[] = {
+ "th", "st", "nd", "rd", "th", "th", "th", "th", "th", "th"
+ };
+
+ if (n == 0) {
+ printf("active");
+ col += 6;
+ return;
+ }
+ if ((n/10)%10 == 1)
+ (void)snprintf(rline, sizeof(rline), "%dth", n);
+ else
+ (void)snprintf(rline, sizeof(rline), "%d%s", n, r[n%10]);
+ col += strlen(rline);
+ printf("%s", rline);
+}
+
+void
+alarmhandler(int signo __unused)
+{
+ /* the signal is ignored */
+ /* (the '__unused' is just to avoid a compile-time warning) */
+}
diff --git a/usr.sbin/lpr/common_source/lp.cdefs.h b/usr.sbin/lpr/common_source/lp.cdefs.h
new file mode 100644
index 000000000000..f5adf6e0ba5d
--- /dev/null
+++ b/usr.sbin/lpr/common_source/lp.cdefs.h
@@ -0,0 +1,109 @@
+/*-
+ * SPDX-License-Identifier: BSD-2-Clause
+ *
+ * ------+---------+---------+---------+---------+---------+---------+---------*
+ * Copyright (c) 2003,2013 - Garance Alistair Drosehn <gad@FreeBSD.org>.
+ * All rights reserved.
+ *
+ * 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.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR 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 AUTHOR 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.
+ *
+ * The views and conclusions contained in the software and documentation
+ * are those of the authors and should not be interpreted as representing
+ * official policies, either expressed or implied, of the FreeBSD Project.
+ *
+ * ------+---------+---------+---------+---------+---------+---------+---------*
+ * ------+---------+---------+---------+---------+---------+---------+---------*
+ */
+
+/*
+ * The main goal of this include file is to provide a platform-neutral way
+ * to define some macros that lpr wants from FreeBSD's <sys/cdefs.h>. This
+ * will simply use the standard <sys/cdefs.h> when compiled in FreeBSD, but
+ * other OS's may not have /usr/include/sys/cdefs.h (or even if that file
+ * exists, it may not define all the macros that lpr will use).
+ */
+
+#if !defined(_LP_CDEFS_H_)
+#define _LP_CDEFS_H_
+
+/*
+ * For non-BSD platforms, you can compile lpr with -DHAVE_SYS_CDEFS_H
+ * if <sys/cdefs.h> should be included.
+ */
+#if defined(__FreeBSD__) || defined(__NetBSD__) || defined(__OpenBSD__)
+# define HAVE_SYS_CDEFS_H
+#endif
+#if defined(HAVE_SYS_CDEFS_H)
+# include <sys/cdefs.h>
+#endif
+
+/*
+ * FreeBSD added a closefrom() routine in release 8.0. When compiling
+ * `lpr' on other platforms you might want to include bsd-closefrom.c
+ * from the portable-openssh project.
+ */
+#ifndef USE_CLOSEFROM
+# if defined(__FreeBSD__) || defined(__NetBSD__) || defined(__OpenBSD__)
+# define USE_CLOSEFROM 1
+# endif
+#endif
+/* The macro USE_CLOSEFROM must be defined with a value of 0 or 1. */
+#ifndef USE_CLOSEFROM
+# define USE_CLOSEFROM 0
+#endif
+
+/*
+ * __unused is a compiler-specific trick which can be used to avoid
+ * warnings about a variable which is defined but never referenced.
+ * Some lpr files use this, so define a null version if it was not
+ * defined by <sys/cdefs.h>.
+ */
+#if !defined(__unused)
+# define __unused
+#endif
+
+/*
+ * Some lpr include files use __BEGIN_DECLS and __END_DECLS.
+ */
+#if !defined(__BEGIN_DECLS)
+# if defined(__cplusplus)
+# define __BEGIN_DECLS extern "C" {
+# define __END_DECLS }
+# else
+# define __BEGIN_DECLS
+# define __END_DECLS
+# endif
+#endif
+
+/*
+ * __printflike and __printf0like are a compiler-specific tricks to
+ * tell the compiler to check the format-codes in printf-like
+ * routines wrt the args that will be formatted.
+ */
+#if !defined(__printflike)
+# define __printflike(fmtarg, firstvararg)
+#endif
+#if !defined(__printf0like)
+# define __printf0like(fmtarg, firstvararg)
+#endif
+
+#endif /* !_LP_CDEFS_H_ */
diff --git a/usr.sbin/lpr/common_source/lp.h b/usr.sbin/lpr/common_source/lp.h
new file mode 100644
index 000000000000..b8c02e26b710
--- /dev/null
+++ b/usr.sbin/lpr/common_source/lp.h
@@ -0,0 +1,316 @@
+/*-
+ * SPDX-License-Identifier: BSD-3-Clause
+ *
+ * Copyright (c) 1983, 1993
+ * The Regents of the University of California. All rights reserved.
+ *
+ * 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/queue.h>
+#include <time.h>
+#include <netdb.h>
+
+/*
+ * All this information used to be in global static variables shared
+ * mysteriously by various parts of the lpr/lpd suite.
+ * This structure attempts to centralize all these declarations in the
+ * hope that they can later be made more dynamic.
+ */
+enum lpd_filters { LPF_CIFPLOT, LPF_DVI, LPF_GRAPH, LPF_INPUT,
+ LPF_DITROFF, LPF_OUTPUT, LPF_FORTRAN, LPF_TROFF,
+ LPF_RASTER, LPF_COUNT };
+/* NB: there is a table in common.c giving the mapping from capability names */
+
+struct printer {
+ char *printer; /* printer name */
+ int remote; /* true if RM points to a remote host */
+ int rp_matches_local; /* true if rp has same name as us */
+ int tof; /* true if we are at top-of-form */
+ /* ------------------------------------------------------ */
+ char *acct_file; /* AF: accounting file */
+ long baud_rate; /* BR: baud rate if lp is a tty */
+ char *filters[LPF_COUNT]; /* CF, DF, GF, IF, NF, OF, RF, TF, VF */
+ long conn_timeout; /* CT: TCP connection timeout */
+ long daemon_user; /* DU: daemon user id -- XXX belongs ???? */
+ char *form_feed; /* FF: form feed */
+ long header_last; /* HL: print header last */
+ char *log_file; /* LF: log file */
+ char *lock_file; /* LO: lock file */
+ char *lp; /* LP: device name or network address */
+ long max_copies; /* MC: maximum number of copies allowed */
+ long max_blocks; /* MX: maximum number of blocks to copy */
+ long price100; /* PC: price per 100 units of output */
+ long page_length; /* PL: page length */
+ long page_width; /* PW: page width */
+ long page_pwidth; /* PX: page width in pixels */
+ long page_plength; /* PY: page length in pixels */
+ long resend_copies; /* RC: resend copies to remote host */
+ char *restrict_grp; /* RG: restricted group */
+ char *remote_host; /* RM: remote machine name */
+ char *remote_queue; /* RP: remote printer name */
+ long restricted; /* RS: restricted to those with local accts */
+ long rw; /* RW: open LP for reading and writing */
+ long short_banner; /* SB: short banner */
+ long no_copies; /* SC: suppress multiple copies */
+ char *spool_dir; /* SD: spool directory */
+ long no_formfeed; /* SF: suppress FF on each print job */
+ long no_header; /* SH: suppress header page */
+ char *stat_recv; /* SR: statistics file, receiving jobs */
+ char *stat_send; /* SS: statistics file, sending jobs */
+ char *status_file; /* ST: status file name */
+ char *trailer; /* TR: trailer string send when Q empties */
+ char *mode_set; /* MS: mode set, a la stty */
+
+ /* variables used by trstat*() to keep statistics on file transfers */
+#define JOBNUM_SIZE 8
+ char jobnum[JOBNUM_SIZE];
+ long jobdfnum; /* current datafile number within job */
+ struct timespec tr_start, tr_done;
+#define TIMESTR_SIZE 40 /* holds result from LPD_TIMESTAMP_PATTERN */
+ char tr_timestr[TIMESTR_SIZE];
+#define DIFFTIME_TS(endTS,startTS) \
+ ((double)(endTS.tv_sec - startTS.tv_sec) \
+ + (endTS.tv_nsec - startTS.tv_nsec) * 1.0e-9)
+};
+
+/*
+ * Lists of user names and job numbers, for the benefit of the structs
+ * defined below. We use TAILQs so that requests don't get mysteriously
+ * reversed in process.
+ */
+struct req_user {
+ TAILQ_ENTRY(req_user) ru_link; /* macro glue */
+ char ru_uname[1]; /* name of user */
+};
+TAILQ_HEAD(req_user_head, req_user);
+
+struct req_file {
+ TAILQ_ENTRY(req_file) rf_link; /* macro glue */
+ char rf_type; /* type (lowercase cf file letter) of file */
+ char *rf_prettyname; /* user-visible name of file */
+ char rf_fname[1]; /* name of file */
+};
+TAILQ_HEAD(req_file_head, req_file);
+
+struct req_jobid {
+ TAILQ_ENTRY(req_jobid) rj_link; /* macro glue */
+ int rj_job; /* job number */
+};
+TAILQ_HEAD(req_jobid_head, req_jobid);
+
+/*
+ * Encapsulate all the information relevant to a request in the
+ * lpr/lpd protocol.
+ */
+enum req_type { REQ_START, REQ_RECVJOB, REQ_LIST, REQ_DELETE };
+
+struct request {
+ enum req_type type; /* what sort of request is this? */
+ struct printer prtr; /* which printer is it for? */
+ int remote; /* did request arrive over network? */
+ char *logname; /* login name of requesting user */
+ char *authname; /* authenticated identity of requesting user */
+ char *prettyname; /* ``pretty'' name of requesting user */
+ int privileged; /* was the request from a privileged user? */
+ void *authinfo; /* authentication information */
+ int authentic; /* was the request securely authenticated? */
+
+ /* Information for queries and deletes... */
+ int nusers; /* length of following list... */
+ struct req_user_head users; /* list of users to query/delete */
+ int njobids; /* length of following list... */
+ struct req_jobid_head jobids; /* list of jobids to query/delete */
+};
+
+/*
+ * Global definitions for the line printer system.
+ */
+extern char line[BUFSIZ];
+extern const char *progname; /* program name (lpr, lpq, etc) */
+
+ /*
+ * 'local_host' is the name of the machine that lpd (lpr, whatever)
+ * is actually running on.
+ *
+ * 'from_host' will point to the 'host' variable when receiving a job
+ * from a user on the same host, or "somewhere else" when receiving a
+ * job from a remote host. If 'from_host != local_host', then 'from_ip'
+ * is the character representation of the IP address of from_host (note
+ * that string could be an IPv6 address).
+ *
+ * Also note that when 'from_host' is not pointing at 'local_host', the
+ * string it is pointing at may be as long as NI_MAXHOST (which is very
+ * likely to be much longer than MAXHOSTNAMELEN).
+ */
+extern char local_host[MAXHOSTNAMELEN];
+extern const char *from_host; /* client's machine name */
+extern const char *from_ip; /* client machine's IP address */
+
+extern int requ[]; /* job number of spool entries */
+extern int requests; /* # of spool requests */
+extern char *user[]; /* users to process */
+extern int users; /* # of users in user array */
+extern char *person; /* name of person doing lprm */
+extern u_char family; /* address family */
+
+/*
+ * Structure used for building a sorted list of control files.
+ * The job_processed value can be used by callers of getq(), to keep
+ * track of whatever processing they are doing.
+ */
+struct jobqueue {
+ time_t job_time; /* last-mod time of cf-file */
+ int job_matched; /* used by match_jobspec() */
+ int job_processed; /* set to zero by getq() */
+ char job_cfname[MAXNAMLEN+1]; /* control file name */
+};
+
+/* lpr/lpd generates readable timestamps for logfiles, etc. Have all those
+ * timestamps be in the same format wrt strftime(). This is ISO 8601 format,
+ * with the addition of an easy-readable day-of-the-week field. Note that
+ * '%T' = '%H:%M:%S', and that '%z' is not available on all platforms.
+ */
+#define LPD_TIMESTAMP_PATTERN "%Y-%m-%dT%T%z %a"
+
+/*
+ * Codes to indicate which statistic records trstat_write should write.
+ */
+typedef enum { TR_SENDING, TR_RECVING, TR_PRINTING } tr_sendrecv;
+
+/*
+ * Error codes for our mini printcap library.
+ */
+#define PCAPERR_TCLOOP (-3)
+#define PCAPERR_OSERR (-2)
+#define PCAPERR_NOTFOUND (-1)
+#define PCAPERR_SUCCESS 0
+#define PCAPERR_TCOPEN 1
+
+/*
+ * File modes for the various status files maintained by lpd.
+ */
+#define LOCK_FILE_MODE (S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH)
+#define LFM_PRINT_DIS (S_IXUSR)
+#define LFM_QUEUE_DIS (S_IXGRP)
+#define LFM_RESET_QUE (S_IXOTH)
+
+#define STAT_FILE_MODE (S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH)
+#define LOG_FILE_MODE (S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH)
+#define TEMP_FILE_MODE (S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH)
+
+/*
+ * Bit-flags for set_qstate() actions, followed by the return values.
+ */
+#define SQS_DISABLEQ 0x01 /* Disable the queuing of new jobs */
+#define SQS_STOPP 0x02 /* Stop the printing of jobs */
+#define SQS_ENABLEQ 0x10 /* Enable the queuing of new jobs */
+#define SQS_STARTP 0x20 /* Start the printing of jobs */
+#define SQS_QCHANGED 0x80 /* The queue has changed (new jobs, etc) */
+
+#define SQS_PARMERR -9 /* Invalid parameters from caller */
+#define SQS_CREFAIL -3 /* File did not exist, and create failed */
+#define SQS_CHGFAIL -2 /* File exists, but unable to change state */
+#define SQS_STATFAIL -1 /* Unable to stat() the lock file */
+#define SQS_CHGOK 1 /* File existed, and the state was changed */
+#define SQS_CREOK 2 /* File did not exist, but was created OK */
+#define SQS_SKIPCREOK 3 /* File did not exist, and there was */
+ /* no need to create it */
+
+/*
+ * Command codes used in the protocol.
+ */
+#define CMD_CHECK_QUE '\1'
+#define CMD_TAKE_THIS '\2'
+#define CMD_SHOWQ_SHORT '\3'
+#define CMD_SHOWQ_LONG '\4'
+#define CMD_RMJOB '\5'
+
+/*
+ * seteuid() macros.
+*/
+
+extern uid_t uid, euid;
+
+#define PRIV_START { \
+ if (seteuid(euid) != 0) err(1, "seteuid failed"); \
+}
+#define PRIV_END { \
+ if (seteuid(uid) != 0) err(1, "seteuid failed"); \
+}
+
+
+#include "lp.cdefs.h" /* A cross-platform version of <sys/cdefs.h> */
+
+__BEGIN_DECLS
+struct dirent;
+
+void blankfill(int _tocol);
+int calc_jobnum(const char *_cfname, const char **_hostpp);
+char *checkremote(struct printer *_pp);
+int chk(char *_file);
+void closeallfds(int _start);
+void delay(int _millisec);
+void displayq(struct printer *_pp, int _format);
+void dump(const char *_nfile, const char *_datafile, int _copies);
+void fatal(const struct printer *_pp, const char *_msg, ...)
+ __printflike(2, 3);
+int firstprinter(struct printer *_pp, int *_error);
+void free_printer(struct printer *_pp);
+void free_request(struct request *_rp);
+int get_line(FILE *_cfp);
+int getport(const struct printer *_pp, const char *_rhost, int _rport);
+int getprintcap(const char *_printer, struct printer *_pp);
+int getq(const struct printer *_pp, struct jobqueue *(*_namelist[]));
+void header(void);
+void inform(const struct printer *_pp, char *_cf);
+void init_printer(struct printer *_pp);
+void init_request(struct request *_rp);
+int inlist(char *_uname, char *_cfile);
+int iscf(const struct dirent *_d);
+void ldump(const char *_nfile, const char *_datafile, int _copies);
+void lastprinter(void);
+int lockchk(struct printer *_pp, char *_slockf);
+char *lock_file_name(const struct printer *_pp, char *_buf, size_t _len);
+void lpd_gettime(struct timespec *_tsp, char *_strp, size_t _strsize);
+int nextprinter(struct printer *_pp, int *_error);
+const
+char *pcaperr(int _error);
+void prank(int _n);
+void process(const struct printer *_pp, char *_file);
+void rmjob(const char *_printer);
+void rmremote(const struct printer *_pp);
+void setprintcap(char *_newfile);
+int set_qstate(int _action, const char *_lfname);
+void show(const char *_nfile, const char *_datafile, int _copies);
+int startdaemon(const struct printer *_pp);
+char *status_file_name(const struct printer *_pp, char *_buf,
+ size_t _len);
+void trstat_init(struct printer *_pp, const char *_fname, int _filenum);
+void trstat_write(struct printer *_pp, tr_sendrecv _sendrecv,
+ size_t _bytecnt, const char *_userid, const char *_otherhost,
+ const char *_orighost);
+ssize_t writel(int _strm, ...);
+__END_DECLS
diff --git a/usr.sbin/lpr/common_source/lp.local.h b/usr.sbin/lpr/common_source/lp.local.h
new file mode 100644
index 000000000000..c4ccef0c2597
--- /dev/null
+++ b/usr.sbin/lpr/common_source/lp.local.h
@@ -0,0 +1,75 @@
+/*-
+ * Copyright (c) 1983, 1993
+ * The Regents of the University of California. All rights reserved.
+ *
+ * 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.
+ */
+
+/*
+ * Possibly, local parameters to the spooling system
+ */
+
+/*
+ * Defaults for line printer capabilities data base
+ */
+#define DEFLP "lp"
+#define DEFLOCK "lock"
+#define DEFSTAT "status"
+#define DEFMX 0
+#define DEFMAXCOPIES 0
+#define DEFFF "\f"
+#define DEFWIDTH 132
+#define DEFLENGTH 66
+#define DEFUID 1
+#define DEFTIMEOUT 120
+
+/*
+ * When files are created in the spooling area, they are normally
+ * readable only by their owner and the spooling group. If you
+ * want otherwise, change this mode.
+ */
+#define FILMOD 0660
+
+/*
+ * Printer is assumed to support LINELEN (for block chars)
+ * and background character (blank) is a space
+ */
+#define LINELEN 132
+#define BACKGND ' '
+
+#define HEIGHT 9 /* height of characters */
+#define WIDTH 8 /* width of characters */
+#define DROP 3 /* offset to drop characters with descenders */
+
+/*
+ * Define TERMCAP if the terminal capabilities are to be used for lpq.
+ */
+#define TERMCAP
+
+/*
+ * Maximum number of user and job requests for lpq and lprm.
+ */
+#define MAXUSERS 50
+#define MAXREQUESTS 50
diff --git a/usr.sbin/lpr/common_source/matchjobs.c b/usr.sbin/lpr/common_source/matchjobs.c
new file mode 100644
index 000000000000..47309cf25702
--- /dev/null
+++ b/usr.sbin/lpr/common_source/matchjobs.c
@@ -0,0 +1,568 @@
+/*-
+ * SPDX-License-Identifier: BSD-2-Clause
+ *
+ * ------+---------+---------+---------+---------+---------+---------+---------*
+ * Copyright (c) 2002,2011 - Garance Alistair Drosehn <gad@FreeBSD.org>.
+ * All rights reserved.
+ *
+ * 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.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR 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 AUTHOR 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.
+ *
+ * The views and conclusions contained in the software and documentation
+ * are those of the authors and should not be interpreted as representing
+ * official policies, either expressed or implied, of the FreeBSD Project
+ * or FreeBSD, Inc.
+ *
+ * ------+---------+---------+---------+---------+---------+---------+---------*
+ */
+
+#include "lp.cdefs.h" /* A cross-platform version of <sys/cdefs.h> */
+/*
+ * movejobs.c - The lpc commands which move jobs around.
+ */
+
+#include <sys/file.h>
+#include <sys/param.h>
+#include <sys/queue.h>
+#include <sys/time.h>
+
+#include <dirent.h> /* for MAXNAMLEN, for job_cfname in lp.h! */
+#include <ctype.h>
+#include <errno.h>
+#include <fnmatch.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+#include "ctlinfo.h"
+#include "lp.h"
+#include "matchjobs.h"
+
+#define DEBUG_PARSEJS 0 /* set to 1 when testing */
+#define DEBUG_SCANJS 0 /* set to 1 when testing */
+
+static int match_jobspec(struct jobqueue *_jq, struct jobspec *_jspec);
+
+/*
+ * isdigit is defined to work on an 'int', in the range 0 to 255, plus EOF.
+ * Define a wrapper which can take 'char', either signed or unsigned.
+ */
+#define isdigitch(Anychar) isdigit(((int) Anychar) & 255)
+
+/*
+ * Format a single jobspec into a string fit for printing.
+ */
+void
+format_jobspec(struct jobspec *jspec, int fmt_wanted)
+{
+ char rangestr[40], buildstr[200];
+ const char fromuser[] = "from user ";
+ const char fromhost[] = "from host ";
+ size_t strsize;
+
+ /*
+ * If the struct already has a fmtstring, then release it
+ * before building a new one.
+ */
+ if (jspec->fmtoutput != NULL) {
+ free(jspec->fmtoutput);
+ jspec->fmtoutput = NULL;
+ }
+
+ jspec->pluralfmt = 1; /* assume a "plural result" */
+ rangestr[0] = '\0';
+ if (jspec->startnum >= 0) {
+ if (jspec->startnum != jspec->endrange)
+ snprintf(rangestr, sizeof(rangestr), "%ld-%ld",
+ jspec->startnum, jspec->endrange);
+ else {
+ jspec->pluralfmt = 0;
+ snprintf(rangestr, sizeof(rangestr), "%ld",
+ jspec->startnum);
+ }
+ }
+
+ strsize = sizeof(buildstr);
+ buildstr[0] = '\0';
+ switch (fmt_wanted) {
+ case FMTJS_TERSE:
+ /* Build everything but the hostname in a temp string. */
+ if (jspec->wanteduser != NULL)
+ strlcat(buildstr, jspec->wanteduser, strsize);
+ if (rangestr[0] != '\0') {
+ if (buildstr[0] != '\0')
+ strlcat(buildstr, ":", strsize);
+ strlcat(buildstr, rangestr, strsize);
+ }
+ if (jspec->wantedhost != NULL)
+ strlcat(buildstr, "@", strsize);
+
+ /* Get space for the final result, including hostname */
+ strsize = strlen(buildstr) + 1;
+ if (jspec->wantedhost != NULL)
+ strsize += strlen(jspec->wantedhost);
+ jspec->fmtoutput = malloc(strsize);
+
+ /* Put together the final result */
+ strlcpy(jspec->fmtoutput, buildstr, strsize);
+ if (jspec->wantedhost != NULL)
+ strlcat(jspec->fmtoutput, jspec->wantedhost, strsize);
+ break;
+
+ case FMTJS_VERBOSE:
+ default:
+ /* Build everything but the hostname in a temp string. */
+ strlcat(buildstr, rangestr, strsize);
+ if (jspec->wanteduser != NULL) {
+ if (rangestr[0] != '\0')
+ strlcat(buildstr, " ", strsize);
+ strlcat(buildstr, fromuser, strsize);
+ strlcat(buildstr, jspec->wanteduser, strsize);
+ }
+ if (jspec->wantedhost != NULL) {
+ if (jspec->wanteduser == NULL) {
+ if (rangestr[0] != '\0')
+ strlcat(buildstr, " ", strsize);
+ strlcat(buildstr, fromhost, strsize);
+ } else
+ strlcat(buildstr, "@", strsize);
+ }
+
+ /* Get space for the final result, including hostname */
+ strsize = strlen(buildstr) + 1;
+ if (jspec->wantedhost != NULL)
+ strsize += strlen(jspec->wantedhost);
+ jspec->fmtoutput = malloc(strsize);
+
+ /* Put together the final result */
+ strlcpy(jspec->fmtoutput, buildstr, strsize);
+ if (jspec->wantedhost != NULL)
+ strlcat(jspec->fmtoutput, jspec->wantedhost, strsize);
+ break;
+ }
+}
+
+/*
+ * Free all the jobspec-related information.
+ */
+void
+free_jobspec(struct jobspec_hdr *js_hdr)
+{
+ struct jobspec *jsinf;
+
+ while (!STAILQ_EMPTY(js_hdr)) {
+ jsinf = STAILQ_FIRST(js_hdr);
+ STAILQ_REMOVE_HEAD(js_hdr, nextjs);
+ if (jsinf->fmtoutput)
+ free(jsinf->fmtoutput);
+ if (jsinf->matcheduser)
+ free(jsinf->matcheduser);
+ free(jsinf);
+ }
+}
+
+/*
+ * This routine takes a string as typed in from the user, and parses it
+ * into a job-specification. A job specification would match one or more
+ * jobs in the queue of some single printer (the specification itself does
+ * not indicate which queue should be searched).
+ *
+ * This recognizes a job-number range by itself (all digits, or a range
+ * indicated by "digits-digits"), or a userid by itself. If a `:' is
+ * found, it is treated as a separator between a job-number range and
+ * a userid, where the job number range is the side which has a digit as
+ * the first character. If an `@' is found, everything to the right of
+ * it is treated as the hostname the job originated from.
+ *
+ * So, the user can specify:
+ * jobrange userid userid:jobrange jobrange:userid
+ * jobrange@hostname jobrange:userid@hostname
+ * userid@hostname userid:jobrange@hostname
+ *
+ * XXX - it would be nice to add "not options" too, such as ^user,
+ * ^jobrange, and @^hostname.
+ *
+ * This routine may modify the original input string if that input is
+ * valid. If the input was *not* valid, then this routine should return
+ * with the input string the same as when the routine was called.
+ */
+int
+parse_jobspec(char *jobstr, struct jobspec_hdr *js_hdr)
+{
+ struct jobspec *jsinfo;
+ char *atsign, *colon, *lhside, *numstr, *period, *rhside;
+ int jobnum;
+
+#if DEBUG_PARSEJS
+ printf("\t [ pjs-input = %s ]\n", jobstr);
+#endif
+
+ if ((jobstr == NULL) || (*jobstr == '\0'))
+ return (0);
+
+ jsinfo = malloc(sizeof(struct jobspec));
+ memset(jsinfo, 0, sizeof(struct jobspec));
+ jsinfo->startnum = jsinfo->endrange = -1;
+
+ /* Find the separator characters, and nullify them. */
+ numstr = NULL;
+ atsign = strchr(jobstr, '@');
+ colon = strchr(jobstr, ':');
+ if (atsign != NULL)
+ *atsign = '\0';
+ if (colon != NULL)
+ *colon = '\0';
+
+ /* The at-sign always indicates a hostname. */
+ if (atsign != NULL) {
+ rhside = atsign + 1;
+ if (*rhside != '\0')
+ jsinfo->wantedhost = rhside;
+ }
+
+ /* Finish splitting the input into three parts. */
+ rhside = NULL;
+ if (colon != NULL) {
+ rhside = colon + 1;
+ if (*rhside == '\0')
+ rhside = NULL;
+ }
+ lhside = NULL;
+ if (*jobstr != '\0')
+ lhside = jobstr;
+
+ /*
+ * If there is a `:' here, then it's either jobrange:userid,
+ * userid:jobrange, or (if @hostname was not given) perhaps it
+ * might be hostname:jobnum. The side which has a digit as the
+ * first character is assumed to be the jobrange. It is an
+ * input error if both sides start with a digit, or if neither
+ * side starts with a digit.
+ */
+ if ((lhside != NULL) && (rhside != NULL)) {
+ if (isdigitch(*lhside)) {
+ if (isdigitch(*rhside))
+ goto bad_input;
+ numstr = lhside;
+ jsinfo->wanteduser = rhside;
+ } else if (isdigitch(*rhside)) {
+ numstr = rhside;
+ /*
+ * The original implementation of 'lpc topq' accepted
+ * hostname:jobnum. If the input did not include a
+ * @hostname, then assume the userid is a hostname if
+ * it includes a '.'.
+ */
+ period = strchr(lhside, '.');
+ if ((atsign == NULL) && (period != NULL))
+ jsinfo->wantedhost = lhside;
+ else
+ jsinfo->wanteduser = lhside;
+ } else {
+ /* Neither side is a job number = user error */
+ goto bad_input;
+ }
+ } else if (lhside != NULL) {
+ if (isdigitch(*lhside))
+ numstr = lhside;
+ else
+ jsinfo->wanteduser = lhside;
+ } else if (rhside != NULL) {
+ if (isdigitch(*rhside))
+ numstr = rhside;
+ else
+ jsinfo->wanteduser = rhside;
+ }
+
+ /*
+ * Break down the numstr. It should be all digits, or a range
+ * specified as "\d+-\d+".
+ */
+ if (numstr != NULL) {
+ errno = 0;
+ jobnum = strtol(numstr, &numstr, 10);
+ if (errno != 0) /* error in conversion */
+ goto bad_input;
+ if (jobnum < 0) /* a bogus value for this purpose */
+ goto bad_input;
+ if (jobnum > 99999) /* too large for job number */
+ goto bad_input;
+ jsinfo->startnum = jsinfo->endrange = jobnum;
+
+ /* Check for a range of numbers */
+ if ((*numstr == '-') && (isdigitch(*(numstr + 1)))) {
+ numstr++;
+ errno = 0;
+ jobnum = strtol(numstr, &numstr, 10);
+ if (errno != 0) /* error in conversion */
+ goto bad_input;
+ if (jobnum < jsinfo->startnum)
+ goto bad_input;
+ if (jobnum > 99999) /* too large for job number */
+ goto bad_input;
+ jsinfo->endrange = jobnum;
+ }
+
+ /*
+ * If there is anything left in the numstr, and if the
+ * original string did not include a userid or a hostname,
+ * then this might be the ancient form of '\d+hostname'
+ * (with no separator between jobnum and hostname). Accept
+ * that for backwards compatibility, but otherwise any
+ * remaining characters mean a user-error. Note that the
+ * ancient form accepted only a single number, but this
+ * will also accept a range of numbers.
+ */
+ if (*numstr != '\0') {
+ if (atsign != NULL)
+ goto bad_input;
+ if (jsinfo->wantedhost != NULL)
+ goto bad_input;
+ if (jsinfo->wanteduser != NULL)
+ goto bad_input;
+ /* Treat as the rest of the string as a hostname */
+ jsinfo->wantedhost = numstr;
+ }
+ }
+
+ if ((jsinfo->startnum < 0) && (jsinfo->wanteduser == NULL) &&
+ (jsinfo->wantedhost == NULL))
+ goto bad_input;
+
+ /*
+ * The input was valid, in the sense that it could be parsed
+ * into the individual parts. Add this jobspec to the list
+ * of jobspecs.
+ */
+ STAILQ_INSERT_TAIL(js_hdr, jsinfo, nextjs);
+
+#if DEBUG_PARSEJS
+ printf("\t [ will check for");
+ if (jsinfo->startnum >= 0) {
+ if (jsinfo->startnum == jsinfo->endrange)
+ printf(" jobnum = %ld", jsinfo->startnum);
+ else
+ printf(" jobrange = %ld to %ld", jsinfo->startnum,
+ jsinfo->endrange);
+ } else {
+ printf(" jobs");
+ }
+ if ((jsinfo->wanteduser != NULL) || (jsinfo->wantedhost != NULL)) {
+ printf(" from");
+ if (jsinfo->wanteduser != NULL)
+ printf(" user = %s", jsinfo->wanteduser);
+ if (jsinfo->wantedhost != NULL)
+ printf(" host = %s", jsinfo->wantedhost);
+ }
+ printf("]\n");
+#endif
+
+ return (1);
+
+bad_input:
+ /*
+ * Restore any `@' and `:', in case the calling routine wants to
+ * write an error message which includes the input string.
+ */
+ if (atsign != NULL)
+ *atsign = '@';
+ if (colon != NULL)
+ *colon = ':';
+ if (jsinfo != NULL)
+ free(jsinfo);
+ return (0);
+}
+
+/*
+ * Check to see if a given job (specified by a jobqueue entry) matches
+ * all of the specifications in a given jobspec.
+ *
+ * Returns 0 if no match, 1 if the job does match.
+ */
+static int
+match_jobspec(struct jobqueue *jq, struct jobspec *jspec)
+{
+ struct cjobinfo *cfinf;
+ const char *cf_hoststr;
+ int jnum, match;
+
+#if DEBUG_SCANJS
+ printf("\t [ match-js checking %s ]\n", jq->job_cfname);
+#endif
+
+ if (jspec == NULL || jq == NULL)
+ return (0);
+
+ /*
+ * Keep track of which jobs have already been matched by this
+ * routine, and thus (probably) already processed.
+ */
+ if (jq->job_matched)
+ return (0);
+
+ jnum = calc_jobnum(jq->job_cfname, &cf_hoststr);
+ cfinf = NULL;
+ match = 0; /* assume the job will not match */
+ jspec->matcheduser = NULL;
+
+ /*
+ * Check the job-number range.
+ */
+ if (jspec->startnum >= 0) {
+ if (jnum < jspec->startnum)
+ goto nomatch;
+ if (jnum > jspec->endrange)
+ goto nomatch;
+ }
+
+ /*
+ * Check the hostname. Strictly speaking this should be done by
+ * reading the control file, but it is less expensive to check
+ * the hostname-part of the control file name. Also, this value
+ * can be easily seen in 'lpq -l', while there is no easy way for
+ * a user/operator to see the hostname in the control file.
+ */
+ if (jspec->wantedhost != NULL) {
+ if (fnmatch(jspec->wantedhost, cf_hoststr, 0) != 0)
+ goto nomatch;
+ }
+
+ /*
+ * Check for a match on the user name. This has to be done
+ * by reading the control file.
+ */
+ if (jspec->wanteduser != NULL) {
+ cfinf = ctl_readcf("fakeq", jq->job_cfname);
+ if (cfinf == NULL)
+ goto nomatch;
+ if (fnmatch(jspec->wanteduser, cfinf->cji_acctuser, 0) != 0)
+ goto nomatch;
+ }
+
+ /* This job matches all of the specified criteria. */
+ match = 1;
+ jq->job_matched = 1; /* avoid matching the job twice */
+ jspec->matchcnt++;
+ if (jspec->wanteduser != NULL) {
+ /*
+ * If the user specified a userid (which may have been a
+ * pattern), then the caller's "doentry()" routine might
+ * want to know the userid of this job that matched.
+ */
+ jspec->matcheduser = strdup(cfinf->cji_acctuser);
+ }
+#if DEBUG_SCANJS
+ printf("\t [ job matched! ]\n");
+#endif
+
+nomatch:
+ if (cfinf != NULL)
+ ctl_freeinf(cfinf);
+ return (match);
+}
+
+/*
+ * Scan a queue for all jobs which match a jobspec. The queue is scanned
+ * from top to bottom.
+ *
+ * The caller can provide a routine which will be executed for each job
+ * that does match. Note that the processing routine might do anything
+ * to the matched job -- including the removal of it.
+ *
+ * This returns the number of jobs which were matched.
+ */
+int
+scanq_jobspec(int qcount, struct jobqueue **squeue, int sopts, struct
+ jobspec_hdr *js_hdr, process_jqe doentry, void *doentryinfo)
+{
+ struct jobqueue **qent;
+ struct jobspec *jspec;
+ int cnt, matched, total;
+
+ if (qcount < 1)
+ return (0);
+ if (js_hdr == NULL)
+ return (-1);
+
+ /* The caller must specify one of the scanning orders */
+ if ((sopts & (SCQ_JSORDER|SCQ_QORDER)) == 0)
+ return (-1);
+
+ total = 0;
+ if (sopts & SCQ_JSORDER) {
+ /*
+ * For each job specification, scan through the queue
+ * looking for every job that matches.
+ */
+ STAILQ_FOREACH(jspec, js_hdr, nextjs) {
+ for (qent = squeue, cnt = 0; cnt < qcount;
+ qent++, cnt++) {
+ matched = match_jobspec(*qent, jspec);
+ if (!matched)
+ continue;
+ total++;
+ if (doentry != NULL)
+ doentry(doentryinfo, *qent, jspec);
+ if (jspec->matcheduser != NULL) {
+ free(jspec->matcheduser);
+ jspec->matcheduser = NULL;
+ }
+ }
+ /*
+ * The entire queue has been scanned for this
+ * jobspec. Call the user's routine again with
+ * a NULL queue-entry, so it can print out any
+ * kind of per-jobspec summary.
+ */
+ if (doentry != NULL)
+ doentry(doentryinfo, NULL, jspec);
+ }
+ } else {
+ /*
+ * For each job in the queue, check all of the job
+ * specifications to see if any one of them matches
+ * that job.
+ */
+ for (qent = squeue, cnt = 0; cnt < qcount;
+ qent++, cnt++) {
+ STAILQ_FOREACH(jspec, js_hdr, nextjs) {
+ matched = match_jobspec(*qent, jspec);
+ if (!matched)
+ continue;
+ total++;
+ if (doentry != NULL)
+ doentry(doentryinfo, *qent, jspec);
+ if (jspec->matcheduser != NULL) {
+ free(jspec->matcheduser);
+ jspec->matcheduser = NULL;
+ }
+ /*
+ * Once there is a match, then there is no
+ * point in checking this same job against
+ * all the other jobspec's.
+ */
+ break;
+ }
+ }
+ }
+
+ return (total);
+}
diff --git a/usr.sbin/lpr/common_source/matchjobs.h b/usr.sbin/lpr/common_source/matchjobs.h
new file mode 100644
index 000000000000..a3c28623e216
--- /dev/null
+++ b/usr.sbin/lpr/common_source/matchjobs.h
@@ -0,0 +1,103 @@
+/*-
+ * SPDX-License-Identifier: BSD-2-Clause
+ *
+ * ------+---------+---------+---------+---------+---------+---------+---------*
+ * Copyright (c) 2002 - Garance Alistair Drosehn <gad@FreeBSD.org>.
+ * All rights reserved.
+ *
+ * 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.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR 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 AUTHOR 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.
+ *
+ * The views and conclusions contained in the software and documentation
+ * are those of the authors and should not be interpreted as representing
+ * official policies, either expressed or implied, of the FreeBSD Project
+ * or FreeBSD, Inc.
+ *
+ * ------+---------+---------+---------+---------+---------+---------+---------*
+ * ------+---------+---------+---------+---------+---------+---------+---------*
+ */
+
+#include <sys/queue.h>
+
+/*
+ * The "matcheduser" field is *only* valid during the call to the
+ * given "doentry()" routine, and is only set if the specification
+ * included a userid.
+ */
+struct jobspec {
+ STAILQ_ENTRY(jobspec) nextjs;
+ char *wantedhost;
+ char *wanteduser;
+ char *matcheduser; /* only valid for "doentry()" */
+ char *fmtoutput; /* set by format_jobspec() */
+ long startnum;
+ long endrange;
+ int pluralfmt; /* boolean set by format_jobspec() */
+ uint matchcnt;
+};
+STAILQ_HEAD(jobspec_hdr, jobspec);
+
+/*
+ * Format options for format_jobspec.
+ */
+#define FMTJS_TERSE 1 /* user:jobrange@host */
+#define FMTJS_VERBOSE 2 /* jobrange from user@host */
+
+/*
+ * Options for scanq_jobspec.
+ *
+ * The caller must choose the order that entries should be scanned:
+ * 1) JSORDER: Matched jobs are processed (by calling the "doentry()"
+ * routine) in the order that the user specified those jobs.
+ * 2) QORDER: Matched jobs are processed in the order that the jobs are
+ * listed the queue. This guarantees that the "doentry()" routine
+ * will be called only once per job.
+ *
+ * There is a "job_matched" variable in struct jobqueue, which is used
+ * to make sure that the "doentry()" will only be called once for any
+ * given job in JSORDER processing. The "doentry()" routine can turn
+ * that off, if it does want to be called multiple times when the job
+ * is matched by multiple specifiers.
+ *
+ * The JSORDER processing will also call the "doentry()" routine once
+ * after each scan of the queue, with the jobqueue set to null. This
+ * provides a way for the caller to print out a summary message for
+ * each jobspec that was given.
+ */
+#define SCQ_JSORDER 0x0001 /* follow the user-specified order */
+#define SCQ_QORDER 0x0002 /* the order of jobs in the queue */
+
+#include "lp.cdefs.h" /* A cross-platform version of <sys/cdefs.h> */
+
+__BEGIN_DECLS
+struct jobqueue;
+
+typedef int process_jqe(void *_myinfo, struct jobqueue *_jq,
+ struct jobspec *_jspec);
+
+void format_jobspec(struct jobspec *_jspec, int _fmt_wanted);
+void free_jobspec(struct jobspec_hdr *_js_hdr);
+int scanq_jobspec(int _qitems, struct jobqueue **_squeue, int _sopts,
+ struct jobspec_hdr *_js_hdr, process_jqe _doentry,
+ void *_doentryinfo);
+int parse_jobspec(char *_jobstr, struct jobspec_hdr *_js_hdr);
+__END_DECLS
+
diff --git a/usr.sbin/lpr/common_source/net.c b/usr.sbin/lpr/common_source/net.c
new file mode 100644
index 000000000000..86af21601f7f
--- /dev/null
+++ b/usr.sbin/lpr/common_source/net.c
@@ -0,0 +1,296 @@
+/*-
+ * SPDX-License-Identifier: BSD-4-Clause
+ *
+ * Copyright (c) 1983, 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. All advertising materials mentioning features or use of this software
+ * must display the following acknowledgement:
+ * This product includes software developed by the University of
+ * California, Berkeley and its contributors.
+ * 4. 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 "lp.cdefs.h" /* A cross-platform version of <sys/cdefs.h> */
+#include <sys/param.h>
+#include <sys/socket.h>
+#include <sys/stat.h>
+#include <sys/time.h>
+#include <sys/uio.h>
+
+#include <netinet/in.h>
+#include <arpa/inet.h>
+#include <netdb.h>
+
+#include <dirent.h> /* required for lp.h, not used here */
+#include <err.h>
+#include <errno.h>
+#include <stdarg.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#include "lp.h"
+#include "lp.local.h"
+#include "pathnames.h"
+
+/*
+ * 'local_host' is always the hostname of the machine which is running
+ * lpr (lpd, whatever), while 'from_host' either points at 'local_host'
+ * or points at a different buffer when receiving a job from a remote
+ * machine (and that buffer has the hostname of that remote machine).
+ */
+char local_host[MAXHOSTNAMELEN]; /* host running lpd/lpr */
+const char *from_host = local_host; /* client's machine name */
+const char *from_ip = ""; /* client machine's IP address */
+
+#ifdef INET6
+u_char family = PF_UNSPEC;
+#else
+u_char family = PF_INET;
+#endif
+
+/*
+ * Create a TCP connection to host "rhost" at port "rport".
+ * If rport == 0, then use the printer service port.
+ * Most of this code comes from rcmd.c.
+ */
+int
+getport(const struct printer *pp, const char *rhost, int rport)
+{
+ struct addrinfo hints, *res, *ai;
+ int s, timo = 1, lport = IPPORT_RESERVED - 1;
+ int error, refused = 0;
+
+ /*
+ * Get the host address and port number to connect to.
+ */
+ if (rhost == NULL)
+ fatal(pp, "no remote host to connect to");
+ memset(&hints, 0, sizeof(hints));
+ hints.ai_family = family;
+ hints.ai_socktype = SOCK_STREAM;
+ hints.ai_protocol = 0;
+ error = getaddrinfo(rhost, (rport == 0 ? "printer" : NULL),
+ &hints, &res);
+ if (error)
+ fatal(pp, "%s\n", gai_strerror(error));
+ if (rport != 0)
+ ((struct sockaddr_in *) res->ai_addr)->sin_port = htons(rport);
+
+ /*
+ * Try connecting to the server.
+ */
+ ai = res;
+retry:
+ PRIV_START
+ s = rresvport_af(&lport, ai->ai_family);
+ PRIV_END
+ if (s < 0) {
+ if (errno != EAGAIN) {
+ if (ai->ai_next) {
+ ai = ai->ai_next;
+ goto retry;
+ }
+ if (refused && timo <= 16) {
+ sleep(timo);
+ timo *= 2;
+ refused = 0;
+ ai = res;
+ goto retry;
+ }
+ }
+ freeaddrinfo(res);
+ return(-1);
+ }
+ if (connect(s, ai->ai_addr, ai->ai_addrlen) < 0) {
+ error = errno;
+ (void) close(s);
+ errno = error;
+ /*
+ * This used to decrement lport, but the current semantics
+ * of rresvport do not provide such a function (in fact,
+ * rresvport should guarantee that the chosen port will
+ * never result in an EADDRINUSE).
+ */
+ if (errno == EADDRINUSE) {
+ goto retry;
+ }
+
+ if (errno == ECONNREFUSED)
+ refused++;
+
+ if (ai->ai_next != NULL) {
+ ai = ai->ai_next;
+ goto retry;
+ }
+ if (refused && timo <= 16) {
+ sleep(timo);
+ timo *= 2;
+ refused = 0;
+ ai = res;
+ goto retry;
+ }
+ freeaddrinfo(res);
+ return(-1);
+ }
+ freeaddrinfo(res);
+ return(s);
+}
+
+/*
+ * Figure out whether the local machine is the same
+ * as the remote machine (RM) entry (if it exists).
+ * We do this by counting the intersection of our
+ * address list and theirs. This is better than the
+ * old method (comparing the canonical names), as it
+ * allows load-sharing between multiple print servers.
+ * The return value is an error message which must be
+ * free()d.
+ */
+char *
+checkremote(struct printer *pp)
+{
+ char lclhost[MAXHOSTNAMELEN];
+ struct addrinfo hints, *local_res, *remote_res, *lr, *rr;
+ char *error;
+ int ncommonaddrs, errno;
+ char h1[NI_MAXHOST], h2[NI_MAXHOST];
+
+ if (!pp->rp_matches_local) { /* Remote printer doesn't match local */
+ pp->remote = 1;
+ return NULL;
+ }
+
+ pp->remote = 0; /* assume printer is local */
+ if (pp->remote_host == NULL)
+ return NULL;
+
+ /* get the addresses of the local host */
+ gethostname(lclhost, sizeof(lclhost));
+ lclhost[sizeof(lclhost) - 1] = '\0';
+
+ memset(&hints, 0, sizeof(hints));
+ hints.ai_family = family;
+ hints.ai_socktype = SOCK_STREAM;
+ hints.ai_flags = AI_PASSIVE;
+ if ((errno = getaddrinfo(lclhost, NULL, &hints, &local_res)) != 0) {
+ asprintf(&error, "unable to get official name "
+ "for local machine %s: %s",
+ lclhost, gai_strerror(errno));
+ return error;
+ }
+
+ /* get the official name of RM */
+ memset(&hints, 0, sizeof(hints));
+ hints.ai_family = family;
+ hints.ai_socktype = SOCK_STREAM;
+ hints.ai_flags = AI_PASSIVE;
+ if ((errno = getaddrinfo(pp->remote_host, NULL,
+ &hints, &remote_res)) != 0) {
+ asprintf(&error, "unable to get address list for "
+ "remote machine %s: %s",
+ pp->remote_host, gai_strerror(errno));
+ freeaddrinfo(local_res);
+ return error;
+ }
+
+ ncommonaddrs = 0;
+ for (lr = local_res; lr; lr = lr->ai_next) {
+ h1[0] = '\0';
+ if (getnameinfo(lr->ai_addr, lr->ai_addrlen, h1, sizeof(h1),
+ NULL, 0, NI_NUMERICHOST) != 0)
+ continue;
+ for (rr = remote_res; rr; rr = rr->ai_next) {
+ h2[0] = '\0';
+ if (getnameinfo(rr->ai_addr, rr->ai_addrlen,
+ h2, sizeof(h2), NULL, 0,
+ NI_NUMERICHOST) != 0)
+ continue;
+ if (strcmp(h1, h2) == 0)
+ ncommonaddrs++;
+ }
+ }
+
+ /*
+ * if the two hosts do not share at least one IP address
+ * then the printer must be remote.
+ */
+ if (ncommonaddrs == 0)
+ pp->remote = 1;
+ freeaddrinfo(local_res);
+ freeaddrinfo(remote_res);
+ return NULL;
+}
+
+/*
+ * This isn't really network-related, but it's used here to write
+ * multi-part strings onto sockets without using stdio. Return
+ * values are as for writev(2).
+ */
+ssize_t
+writel(int strm, ...)
+{
+ va_list ap;
+ int i, n;
+ const char *cp;
+#define NIOV 12
+ struct iovec iov[NIOV], *iovp = iov;
+ ssize_t retval;
+
+ /* first count them */
+ va_start(ap, strm);
+ n = 0;
+ do {
+ cp = va_arg(ap, char *);
+ n++;
+ } while (cp);
+ va_end(ap);
+ n--; /* correct for count of trailing null */
+
+ if (n > NIOV) {
+ iovp = malloc(n * sizeof *iovp);
+ if (iovp == NULL)
+ return -1;
+ }
+
+ /* now make up iovec and send */
+ va_start(ap, strm);
+ for (i = 0; i < n; i++) {
+ iovp[i].iov_base = va_arg(ap, char *);
+ iovp[i].iov_len = strlen(iovp[i].iov_base);
+ }
+ va_end(ap);
+ retval = writev(strm, iovp, n);
+ if (iovp != iov)
+ free(iovp);
+ return retval;
+}
diff --git a/usr.sbin/lpr/common_source/pathnames.h b/usr.sbin/lpr/common_source/pathnames.h
new file mode 100644
index 000000000000..2704c2245b53
--- /dev/null
+++ b/usr.sbin/lpr/common_source/pathnames.h
@@ -0,0 +1,47 @@
+/*-
+ * SPDX-License-Identifier: BSD-3-Clause
+ *
+ * Copyright (c) 1989, 1993
+ * The Regents of the University of California. All rights reserved.
+ *
+ * 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 <paths.h>
+
+#define _PATH_DEFDEVLP "/dev/lp"
+#define _PATH_DEFSPOOL "/var/spool/output/lpd"
+#define _PATH_HOSTSEQUIV "/etc/hosts.equiv"
+#define _PATH_HOSTSLPD "/etc/hosts.lpd"
+#define _PATH_MASTERLOCK "/var/spool/output/lpd.lock"
+#define _PATH_PR "/usr/bin/pr"
+#define _PATH_PRINTCAP "/etc/printcap"
+#define _PATH_SOCKETNAME "/var/run/printer"
+#define _PATH_VFONT "/usr/libdata/vfont/"
+#define _PATH_VFONTB "/usr/libdata/vfont/B"
+#define _PATH_VFONTI "/usr/libdata/vfont/I"
+#define _PATH_VFONTR "/usr/libdata/vfont/R"
+#define _PATH_VFONTS "/usr/libdata/vfont/S"
+#define _PATH_CHKPRINTCAP "/usr/sbin/chkprintcap"
diff --git a/usr.sbin/lpr/common_source/printcap.c b/usr.sbin/lpr/common_source/printcap.c
new file mode 100644
index 000000000000..35fcab4c16eb
--- /dev/null
+++ b/usr.sbin/lpr/common_source/printcap.c
@@ -0,0 +1,439 @@
+/*-
+ * SPDX-License-Identifier: BSD-4-Clause
+ *
+ * Copyright (c) 1983, 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. All advertising materials mentioning features or use of this software
+ * must display the following acknowledgement:
+ * This product includes software developed by the University of
+ * California, Berkeley and its contributors.
+ * 4. 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 "lp.cdefs.h" /* A cross-platform version of <sys/cdefs.h> */
+#include <errno.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#include <sys/param.h> /* required for lp.h, but not used here */
+#include <sys/dirent.h> /* ditto */
+#include "lp.h"
+#include "lp.local.h"
+#include "pathnames.h"
+
+/*
+ * Routines and data used in processing the printcap file.
+ */
+static char *printcapdb[] = { __DECONST(char *, _PATH_PRINTCAP), NULL };
+
+static char *capdb_canonical_name(const char *_bp);
+static int capdb_getaltlog(char *_bp, const char *_shrt,
+ const char *_lng);
+static int capdb_getaltnum(char *_bp, const char *_shrt,
+ const char *_lng, long _dflt, long *_result);
+static int capdb_getaltstr(char *_bp, const char *_shrt,
+ const char *lng, const char *_dflt, char **_result);
+static int getprintcap_int(char *_bp, struct printer *_pp);
+
+/*
+ * Change the name of the printcap file. Used by chkprintcap(8),
+ * but could be used by other members of the suite with appropriate
+ * security measures.
+ */
+void
+setprintcap(char *newfile)
+{
+ printcapdb[0] = newfile;
+}
+
+/*
+ * Read the printcap database for printer `printer' into the
+ * struct printer pointed by `pp'. Return values are as for
+ * cgetent(3): -1 means we could not find what we wanted, -2
+ * means a system error occurred (and errno is set), -3 if a
+ * reference (`tc=') loop was detected, and 0 means success.
+ *
+ * Copied from lpr; should add additional capabilities as they
+ * are required by the other programs in the suite so that
+ * printcap-reading is consistent across the entire family.
+ */
+int
+getprintcap(const char *printer, struct printer *pp)
+{
+ int status;
+ char *bp;
+
+ if ((status = cgetent(&bp, printcapdb, printer)) < 0)
+ return status;
+ status = getprintcap_int(bp, pp);
+ free(bp);
+ return status;
+}
+
+/*
+ * Map the status values returned by cgetfirst/cgetnext into those
+ * used by cgetent, returning truth if there are more records to
+ * examine. This points out what is arguably a bug in the cget*
+ * interface (or at least a nasty wart).
+ */
+static int
+firstnextmap(int *status)
+{
+ switch (*status) {
+ case 0:
+ return 0;
+ case 1:
+ *status = 0;
+ return 1;
+ case 2:
+ *status = 1;
+ return 1;
+ case -1:
+ *status = -2;
+ return 0;
+ case -2:
+ *status = -3;
+ return 1;
+ default:
+ return 0;
+ }
+}
+
+/*
+ * Scan through the database of printers using cgetfirst/cgetnext.
+ * Return false of error or end-of-database; else true.
+ */
+int
+firstprinter(struct printer *pp, int *error)
+{
+ int status;
+ char *bp;
+
+ init_printer(pp);
+ status = cgetfirst(&bp, printcapdb);
+ if (firstnextmap(&status) == 0) {
+ if (error)
+ *error = status;
+ return 0;
+ }
+ if (error)
+ *error = status;
+ status = getprintcap_int(bp, pp);
+ free(bp);
+ if (error && status)
+ *error = status;
+ return 1;
+}
+
+int
+nextprinter(struct printer *pp, int *error)
+{
+ int status;
+ char *bp;
+
+ free_printer(pp);
+ status = cgetnext(&bp, printcapdb);
+ if (firstnextmap(&status) == 0) {
+ if (error)
+ *error = status;
+ return 0;
+ }
+ if (error)
+ *error = status;
+ status = getprintcap_int(bp, pp);
+ free(bp);
+ if (error && status)
+ *error = status;
+ return 1;
+}
+
+void
+lastprinter(void)
+{
+ cgetclose();
+}
+
+/*
+ * This must match the order of declaration of enum filter in lp.h.
+ */
+static const char *filters[] = {
+ "cf", "df", "gf", "if", "nf", "of", "rf", "tf", "vf"
+};
+
+static const char *longfilters[] = {
+ "filt.cifplot", "filt.dvi", "filt.plot", "filt.input", "filt.ditroff",
+ "filt.output", "filt.fortran", "filt.troff", "filt.raster"
+};
+
+/*
+ * Internal routine for both getprintcap() and nextprinter().
+ * Actually parse the printcap entry using cget* functions.
+ * Also attempt to figure out the canonical name of the printer
+ * and store a malloced copy of it in pp->printer.
+ */
+static int
+getprintcap_int(char *bp, struct printer *pp)
+{
+ enum lpd_filters filt;
+ char *rp_name;
+ int error;
+
+ if ((pp->printer = capdb_canonical_name(bp)) == NULL)
+ return PCAPERR_OSERR;
+
+#define CHK(x) do {if ((x) == PCAPERR_OSERR) return PCAPERR_OSERR;}while(0)
+ CHK(capdb_getaltstr(bp, "af", "acct.file", 0, &pp->acct_file));
+ CHK(capdb_getaltnum(bp, "br", "tty.rate", 0, &pp->baud_rate));
+ CHK(capdb_getaltnum(bp, "ct", "remote.timeout", DEFTIMEOUT,
+ &pp->conn_timeout));
+ CHK(capdb_getaltnum(bp, "du", "daemon.user", DEFUID,
+ &pp->daemon_user));
+ CHK(capdb_getaltstr(bp, "ff", "job.formfeed", DEFFF, &pp->form_feed));
+ CHK(capdb_getaltstr(bp, "lf", "spool.log", _PATH_CONSOLE,
+ &pp->log_file));
+ CHK(capdb_getaltstr(bp, "lo", "spool.lock", DEFLOCK, &pp->lock_file));
+ CHK(capdb_getaltstr(bp, "lp", "tty.device", _PATH_DEFDEVLP, &pp->lp));
+ CHK(capdb_getaltnum(bp, "mc", "max.copies", DEFMAXCOPIES,
+ &pp->max_copies));
+ CHK(capdb_getaltstr(bp, "ms", "tty.mode", 0, &pp->mode_set));
+ CHK(capdb_getaltnum(bp, "mx", "max.blocks", DEFMX, &pp->max_blocks));
+ CHK(capdb_getaltnum(bp, "pc", "acct.price", 0, &pp->price100));
+ CHK(capdb_getaltnum(bp, "pl", "page.length", DEFLENGTH,
+ &pp->page_length));
+ CHK(capdb_getaltnum(bp, "pw", "page.width", DEFWIDTH,
+ &pp->page_width));
+ CHK(capdb_getaltnum(bp, "px", "page.pwidth", 0, &pp->page_pwidth));
+ CHK(capdb_getaltnum(bp, "py", "page.plength", 0, &pp->page_plength));
+ CHK(capdb_getaltstr(bp, "rg", "daemon.restrictgrp", 0,
+ &pp->restrict_grp));
+ CHK(capdb_getaltstr(bp, "rm", "remote.host", 0, &pp->remote_host));
+ CHK(capdb_getaltstr(bp, "rp", "remote.queue", DEFLP,
+ &pp->remote_queue));
+ CHK(capdb_getaltstr(bp, "sd", "spool.dir", _PATH_DEFSPOOL,
+ &pp->spool_dir));
+ CHK(capdb_getaltstr(bp, "sr", "stat.recv", 0, &pp->stat_recv));
+ CHK(capdb_getaltstr(bp, "ss", "stat.send", 0, &pp->stat_send));
+ CHK(capdb_getaltstr(bp, "st", "spool.status", DEFSTAT,
+ &pp->status_file));
+ CHK(capdb_getaltstr(bp, "tr", "job.trailer", 0, &pp->trailer));
+
+ pp->resend_copies = capdb_getaltlog(bp, "rc", "remote.resend_copies");
+ pp->restricted = capdb_getaltlog(bp, "rs", "daemon.restricted");
+ pp->short_banner = capdb_getaltlog(bp, "sb", "banner.short");
+ pp->no_copies = capdb_getaltlog(bp, "sc", "job.no_copies");
+ pp->no_formfeed = capdb_getaltlog(bp, "sf", "job.no_formfeed");
+ pp->no_header = capdb_getaltlog(bp, "sh", "banner.disable");
+ pp->header_last = capdb_getaltlog(bp, "hl", "banner.last");
+ pp->rw = capdb_getaltlog(bp, "rw", "tty.rw");
+ pp->tof = !capdb_getaltlog(bp, "fo", "job.topofform");
+
+ /*
+ * Decide if the remote printer name matches the local printer name.
+ * If no name is given then we assume they mean them to match.
+ * If a name is given see if the rp_name is one of the names for
+ * this printer.
+ */
+ pp->rp_matches_local = 1;
+ CHK((error = capdb_getaltstr(bp, "rp", "remote.queue", 0, &rp_name)));
+ if (error != PCAPERR_NOTFOUND && rp_name != NULL) {
+ if (cgetmatch(bp,rp_name) != 0)
+ pp->rp_matches_local = 0;
+ free(rp_name);
+ }
+
+ /*
+ * Filters:
+ */
+ for (filt = 0; filt < LPF_COUNT; filt++) {
+ CHK(capdb_getaltstr(bp, filters[filt], longfilters[filt], 0,
+ &pp->filters[filt]));
+ }
+
+ return 0;
+}
+
+/*
+ * Decode the error codes returned by cgetent() using the names we
+ * made up for them from "lp.h".
+ * This would have been much better done with Common Error, >sigh<.
+ * Perhaps this can be fixed in the next incarnation of cget*.
+ */
+const char *
+pcaperr(int error)
+{
+ switch(error) {
+ case PCAPERR_TCOPEN:
+ return "unresolved tc= expansion";
+ case PCAPERR_SUCCESS:
+ return "no error";
+ case PCAPERR_NOTFOUND:
+ return "printer not found";
+ case PCAPERR_OSERR:
+ return strerror(errno);
+ case PCAPERR_TCLOOP:
+ return "loop detected in tc= expansion";
+ default:
+ return "unknown printcap error";
+ }
+}
+
+/*
+ * Initialize a `struct printer' to contain values harmless to
+ * the other routines in liblpr.
+ */
+void
+init_printer(struct printer *pp)
+{
+ static struct printer zero;
+ *pp = zero;
+}
+
+/*
+ * Free the dynamically-allocated strings in a `struct printer'.
+ * Idempotent.
+ */
+void
+free_printer(struct printer *pp)
+{
+ enum lpd_filters filt;
+#define cfree(x) do { if (x) free(x); } while(0)
+ cfree(pp->printer);
+ cfree(pp->acct_file);
+ for (filt = 0; filt < LPF_COUNT; filt++)
+ cfree(pp->filters[filt]);
+ cfree(pp->form_feed);
+ cfree(pp->log_file);
+ cfree(pp->lock_file);
+ cfree(pp->lp);
+ cfree(pp->restrict_grp);
+ cfree(pp->remote_host);
+ cfree(pp->remote_queue);
+ cfree(pp->spool_dir);
+ cfree(pp->stat_recv);
+ cfree(pp->stat_send);
+ cfree(pp->status_file);
+ cfree(pp->trailer);
+ cfree(pp->mode_set);
+
+ init_printer(pp);
+}
+
+
+/*
+ * The following routines are part of what would be a sensible library
+ * interface to capability databases. Maybe someday this will become
+ * the default.
+ */
+
+/*
+ * It provides similar functionality to cgetstr(),
+ * except that it provides for both a long and a short
+ * capability name and allows for a default to be specified.
+ */
+static int
+capdb_getaltstr(char *bp, const char *shrt, const char *lng,
+ const char *dflt, char **result)
+{
+ int status;
+
+ status = cgetstr(bp, lng, result);
+ if (status >= 0 || status == PCAPERR_OSERR)
+ return status;
+ status = cgetstr(bp, shrt, result);
+ if (status >= 0 || status == PCAPERR_OSERR)
+ return status;
+ if (dflt) {
+ *result = strdup(dflt);
+ if (*result == NULL)
+ return PCAPERR_OSERR;
+ return strlen(*result);
+ }
+ return PCAPERR_NOTFOUND;
+}
+
+/*
+ * The same, only for integers.
+ */
+static int
+capdb_getaltnum(char *bp, const char *shrt, const char *lng, long dflt,
+ long *result)
+{
+ int status;
+
+ status = cgetnum(bp, lng, result);
+ if (status >= 0)
+ return status;
+ status = cgetnum(bp, shrt, result);
+ if (status >= 0)
+ return status;
+ *result = dflt;
+ return 0;
+}
+
+/*
+ * Likewise for logical values. There's no need for a default parameter
+ * because the default is always false.
+ */
+static int
+capdb_getaltlog(char *bp, const char *shrt, const char *lng)
+{
+ if (cgetcap(bp, lng, ':'))
+ return 1;
+ if (cgetcap(bp, shrt, ':'))
+ return 1;
+ return 0;
+}
+
+/*
+ * Also should be a part of a better cget* library.
+ * Given a capdb entry, attempt to figure out what its canonical name
+ * is, and return a malloced copy of it. The canonical name is
+ * considered to be the first one listed.
+ */
+static char *
+capdb_canonical_name(const char *bp)
+{
+ char *retval;
+ const char *nameend;
+
+ nameend = strpbrk(bp, "|:");
+ if (nameend == NULL)
+ nameend = bp + 1;
+ if ((retval = malloc(nameend - bp + 1)) != NULL) {
+ retval[0] = '\0';
+ strncat(retval, bp, nameend - bp);
+ }
+ return retval;
+}
+
+
diff --git a/usr.sbin/lpr/common_source/request.c b/usr.sbin/lpr/common_source/request.c
new file mode 100644
index 000000000000..e0a41a1161e0
--- /dev/null
+++ b/usr.sbin/lpr/common_source/request.c
@@ -0,0 +1,79 @@
+/*
+ * Copyright 1997 Massachusetts Institute of Technology
+ *
+ * Permission to use, copy, modify, and distribute this software and
+ * its documentation for any purpose and without fee is hereby
+ * granted, provided that both the above copyright notice and this
+ * permission notice appear in all copies, that both the above
+ * copyright notice and this permission notice appear in all
+ * supporting documentation, and that the name of M.I.T. not be used
+ * in advertising or publicity pertaining to distribution of the
+ * software without specific, written prior permission. M.I.T. makes
+ * no representations about the suitability of this software for any
+ * purpose. It is provided "as is" without express or implied
+ * warranty.
+ *
+ * THIS SOFTWARE IS PROVIDED BY M.I.T. ``AS IS''. M.I.T. DISCLAIMS
+ * ALL EXPRESS OR IMPLIED WARRANTIES WITH REGARD TO THIS SOFTWARE,
+ * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. IN NO EVENT
+ * SHALL M.I.T. 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.
+ */
+
+static const char copyright[] =
+ "Copyright (C) 1997, Massachusetts Institute of Technology\r\n";
+
+#include "lp.cdefs.h" /* A cross-platform version of <sys/cdefs.h> */
+#include <sys/types.h>
+#include <sys/stat.h>
+
+#include <stdlib.h>
+#include <unistd.h>
+
+#include <sys/param.h> /* needed for lp.h but not used here */
+#include <dirent.h> /* ditto */
+#include <stdio.h> /* ditto */
+#include "lp.h"
+#include "lp.local.h"
+
+void
+init_request(struct request *rp)
+{
+ static struct request zero;
+
+ *rp = zero;
+ TAILQ_INIT(&rp->users);
+ TAILQ_INIT(&rp->jobids);
+}
+
+void
+free_request(struct request *rp)
+{
+ struct req_user *ru;
+ struct req_jobid *rj;
+
+ if (rp->logname)
+ free(rp->logname);
+ if (rp->authname)
+ free(rp->authname);
+ if (rp->prettyname)
+ free(rp->prettyname);
+ if (rp->authinfo)
+ free(rp->authinfo);
+ while ((ru = TAILQ_FIRST(&rp->users)) != NULL) {
+ TAILQ_REMOVE(&rp->users, ru, ru_link);
+ free(ru);
+ }
+ while ((rj = TAILQ_FIRST(&rp->jobids)) != NULL) {
+ TAILQ_REMOVE(&rp->jobids, rj, rj_link);
+ free(rj);
+ }
+ init_request(rp);
+}
diff --git a/usr.sbin/lpr/common_source/rmjob.c b/usr.sbin/lpr/common_source/rmjob.c
new file mode 100644
index 000000000000..b3af8bf5ad3f
--- /dev/null
+++ b/usr.sbin/lpr/common_source/rmjob.c
@@ -0,0 +1,386 @@
+/*-
+ * SPDX-License-Identifier: BSD-3-Clause
+ *
+ * Copyright (c) 1983, 1993
+ * The Regents of the University of California. All rights reserved.
+ *
+ * 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 "lp.cdefs.h" /* A cross-platform version of <sys/cdefs.h> */
+#include <sys/param.h>
+#include <sys/uio.h>
+
+#include <ctype.h>
+#include <dirent.h>
+#include <err.h>
+#include <errno.h>
+#include <signal.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#define psignal foil_gcc_psignal
+#define sys_siglist foil_gcc_siglist
+#include <unistd.h>
+#undef psignal
+#undef sys_siglist
+
+#include "lp.h"
+#include "lp.local.h"
+#include "pathnames.h"
+
+/*
+ * rmjob - remove the specified jobs from the queue.
+ */
+
+/*
+ * Stuff for handling lprm specifications
+ */
+static char root[] = "root";
+static int all = 0; /* eliminate all files (root only) */
+static int cur_daemon; /* daemon's pid */
+static char current[7+MAXHOSTNAMELEN]; /* active control file name */
+
+static void alarmhandler(int _signo);
+static void do_unlink(char *_file);
+static int isowner(char *_owner, char *_file, const char *_cfhost);
+
+void
+rmjob(const char *printer)
+{
+ register int i, nitems;
+ int assassinated = 0;
+ struct dirent **files;
+ char *cp;
+ struct printer myprinter, *pp = &myprinter;
+
+ init_printer(pp);
+ if ((i = getprintcap(printer, pp)) < 0)
+ fatal(pp, "getprintcap: %s", pcaperr(i));
+ if ((cp = checkremote(pp))) {
+ printf("Warning: %s\n", cp);
+ free(cp);
+ }
+
+ /*
+ * If the format was `lprm -' and the user isn't the super-user,
+ * then fake things to look like he said `lprm user'.
+ */
+ if (users < 0) {
+ if (getuid() == 0)
+ all = 1; /* all files in local queue */
+ else {
+ user[0] = person;
+ users = 1;
+ }
+ }
+ if (!strcmp(person, "-all")) {
+ if (from_host == local_host)
+ fatal(pp, "The login name \"-all\" is reserved");
+ all = 1; /* all those from 'from_host' */
+ person = root;
+ }
+
+ PRIV_START
+ if (chdir(pp->spool_dir) < 0)
+ fatal(pp, "cannot chdir to spool directory");
+ if ((nitems = scandir(".", &files, iscf, NULL)) < 0)
+ fatal(pp, "cannot access spool directory");
+ PRIV_END
+
+ if (nitems) {
+ /*
+ * Check for an active printer daemon (in which case we
+ * kill it if it is reading our file) then remove stuff
+ * (after which we have to restart the daemon).
+ */
+ if (lockchk(pp, pp->lock_file) && chk(current)) {
+ PRIV_START
+ assassinated = kill(cur_daemon, SIGINT) == 0;
+ PRIV_END
+ if (!assassinated)
+ fatal(pp, "cannot kill printer daemon");
+ }
+ /*
+ * process the files
+ */
+ for (i = 0; i < nitems; i++)
+ process(pp, files[i]->d_name);
+ }
+ rmremote(pp);
+ /*
+ * Restart the printer daemon if it was killed
+ */
+ if (assassinated && !startdaemon(pp))
+ fatal(pp, "cannot restart printer daemon\n");
+ exit(0);
+}
+
+/*
+ * Process a lock file: collect the pid of the active
+ * daemon and the file name of the active spool entry.
+ * Return boolean indicating existence of a lock file.
+ */
+int
+lockchk(struct printer *pp, char *slockf)
+{
+ register FILE *fp;
+ register int i, n;
+
+ PRIV_START
+ if ((fp = fopen(slockf, "r")) == NULL) {
+ if (errno == EACCES)
+ fatal(pp, "%s: %s", slockf, strerror(errno));
+ else
+ return(0);
+ }
+ PRIV_END
+ if (!get_line(fp)) {
+ (void) fclose(fp);
+ return(0); /* no daemon present */
+ }
+ cur_daemon = atoi(line);
+ if (kill(cur_daemon, 0) < 0 && errno != EPERM) {
+ (void) fclose(fp);
+ return(0); /* no daemon present */
+ }
+ for (i = 1; (n = fread(current, sizeof(char), sizeof(current), fp)) <= 0; i++) {
+ if (i > 5) {
+ n = 1;
+ break;
+ }
+ sleep(i);
+ }
+ current[n-1] = '\0';
+ (void) fclose(fp);
+ return(1);
+}
+
+/*
+ * Process a control file.
+ */
+void
+process(const struct printer *pp, char *file)
+{
+ FILE *cfp;
+
+ if (!chk(file))
+ return;
+ PRIV_START
+ if ((cfp = fopen(file, "r")) == NULL)
+ fatal(pp, "cannot open %s", file);
+ PRIV_END
+ while (get_line(cfp)) {
+ switch (line[0]) {
+ case 'U': /* unlink associated files */
+ if (strchr(line+1, '/') || strncmp(line+1, "df", 2))
+ break;
+ do_unlink(line+1);
+ }
+ }
+ (void) fclose(cfp);
+ do_unlink(file);
+}
+
+static void
+do_unlink(char *file)
+{
+ int ret;
+
+ if (from_host != local_host)
+ printf("%s: ", local_host);
+ PRIV_START
+ ret = unlink(file);
+ PRIV_END
+ printf(ret ? "cannot dequeue %s\n" : "%s dequeued\n", file);
+}
+
+/*
+ * Do the dirty work in checking
+ */
+int
+chk(char *file)
+{
+ int *r, jnum;
+ char **u;
+ const char *cfhost;
+ FILE *cfp;
+
+ /*
+ * Check for valid cf file name (mostly checking current).
+ */
+ if (strlen(file) < 7 || file[0] != 'c' || file[1] != 'f')
+ return(0);
+
+ jnum = calc_jobnum(file, &cfhost);
+ if (all && (from_host == local_host || !strcmp(from_host, cfhost)))
+ return(1);
+
+ /*
+ * get the owner's name from the control file.
+ */
+ PRIV_START
+ if ((cfp = fopen(file, "r")) == NULL)
+ return(0);
+ PRIV_END
+ while (get_line(cfp)) {
+ if (line[0] == 'P')
+ break;
+ }
+ (void) fclose(cfp);
+ if (line[0] != 'P')
+ return(0);
+
+ if (users == 0 && requests == 0)
+ return(!strcmp(file, current) && isowner(line+1, file, cfhost));
+ /*
+ * Check the request list
+ */
+ for (r = requ; r < &requ[requests]; r++)
+ if (*r == jnum && isowner(line+1, file, cfhost))
+ return(1);
+ /*
+ * Check to see if it's in the user list
+ */
+ for (u = user; u < &user[users]; u++)
+ if (!strcmp(*u, line+1) && isowner(line+1, file, cfhost))
+ return(1);
+ return(0);
+}
+
+/*
+ * If root is removing a file on the local machine, allow it.
+ * If root is removing a file from a remote machine, only allow
+ * files sent from the remote machine to be removed.
+ * Normal users can only remove the file from where it was sent.
+ */
+static int
+isowner(char *owner, char *file, const char *cfhost)
+{
+ if (!strcmp(person, root) && (from_host == local_host ||
+ !strcmp(from_host, cfhost)))
+ return (1);
+ if (!strcmp(person, owner) && !strcmp(from_host, cfhost))
+ return (1);
+ if (from_host != local_host)
+ printf("%s: ", local_host);
+ printf("%s: Permission denied\n", file);
+ return(0);
+}
+
+/*
+ * Check to see if we are sending files to a remote machine. If we are,
+ * then try removing files on the remote machine.
+ */
+void
+rmremote(const struct printer *pp)
+{
+ int i, elem, firstreq, niov, rem, totlen;
+ char buf[BUFSIZ];
+ void (*savealrm)(int);
+ struct iovec *iov;
+
+ if (!pp->remote)
+ return; /* not sending to a remote machine */
+
+ /*
+ * Flush stdout so the user can see what has been deleted
+ * while we wait (possibly) for the connection.
+ */
+ fflush(stdout);
+
+ /*
+ * Counting:
+ * 4 == "\5" + remote_queue + " " + person
+ * 2 * users == " " + user[i] for each user
+ * requests == asprintf results for each request
+ * 1 == "\n"
+ * Although laborious, doing it this way makes it possible for
+ * us to process requests of indeterminate length without
+ * applying an arbitrary limit. Arbitrary Limits Are Bad (tm).
+ */
+ if (users > 0)
+ niov = 4 + 2 * users + requests + 1;
+ else
+ niov = 4 + requests + 1;
+ iov = malloc(niov * sizeof *iov);
+ if (iov == NULL)
+ fatal(pp, "out of memory in rmremote()");
+ iov[0].iov_base = "\5";
+ iov[1].iov_base = pp->remote_queue;
+ iov[2].iov_base = " ";
+ iov[3].iov_base = all ? "-all" : person;
+ elem = 4;
+ for (i = 0; i < users; i++) {
+ iov[elem].iov_base = " ";
+ iov[elem + 1].iov_base = user[i];
+ elem += 2;
+ }
+ firstreq = elem;
+ for (i = 0; i < requests; i++) {
+ asprintf((char **)&iov[elem].iov_base, " %d", requ[i]);
+ if (iov[elem].iov_base == 0)
+ fatal(pp, "out of memory in rmremote()");
+ elem++;
+ }
+ iov[elem++].iov_base = "\n";
+ for (totlen = i = 0; i < niov; i++)
+ totlen += (iov[i].iov_len = strlen(iov[i].iov_base));
+
+ savealrm = signal(SIGALRM, alarmhandler);
+ alarm(pp->conn_timeout);
+ rem = getport(pp, pp->remote_host, 0);
+ (void)signal(SIGALRM, savealrm);
+ if (rem < 0) {
+ if (from_host != local_host)
+ printf("%s: ", local_host);
+ printf("connection to %s is down\n", pp->remote_host);
+ } else {
+ if (writev(rem, iov, niov) != totlen)
+ fatal(pp, "Lost connection");
+ while ((i = read(rem, buf, sizeof(buf))) > 0)
+ (void) fwrite(buf, 1, i, stdout);
+ (void) close(rem);
+ }
+ for (i = 0; i < requests; i++)
+ free(iov[firstreq + i].iov_base);
+ free(iov);
+}
+
+/*
+ * Return 1 if the filename begins with 'cf'
+ */
+int
+iscf(const struct dirent *d)
+{
+ return(d->d_name[0] == 'c' && d->d_name[1] == 'f');
+}
+
+void
+alarmhandler(int signo __unused)
+{
+ /* the signal is ignored */
+ /* (the '__unused' is just to avoid a compile-time warning) */
+}
diff --git a/usr.sbin/lpr/common_source/startdaemon.c b/usr.sbin/lpr/common_source/startdaemon.c
new file mode 100644
index 000000000000..d37dd0817043
--- /dev/null
+++ b/usr.sbin/lpr/common_source/startdaemon.c
@@ -0,0 +1,99 @@
+/*-
+ * SPDX-License-Identifier: BSD-3-Clause
+ *
+ * Copyright (c) 1983, 1993, 1994
+ * The Regents of the University of California. All rights reserved.
+ *
+ * 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 "lp.cdefs.h" /* A cross-platform version of <sys/cdefs.h> */
+#include <sys/param.h>
+#include <sys/socket.h>
+#include <sys/uio.h>
+#include <sys/un.h>
+
+#include <dirent.h>
+#include <err.h>
+#include <stdio.h>
+#include <string.h>
+#include <unistd.h>
+#include "lp.h"
+#include "pathnames.h"
+
+/*
+ * Tell the printer daemon that there are new files in the spool directory.
+ */
+
+int
+startdaemon(const struct printer *pp)
+{
+ struct sockaddr_un un;
+ register int s, n;
+ int connectres;
+ char c;
+
+ s = socket(PF_LOCAL, SOCK_STREAM, 0);
+ if (s < 0) {
+ warn("socket");
+ return(0);
+ }
+ memset(&un, 0, sizeof(un));
+ un.sun_family = AF_LOCAL;
+ strcpy(un.sun_path, _PATH_SOCKETNAME);
+#ifndef SUN_LEN
+#define SUN_LEN(unp) (strlen((unp)->sun_path) + 2)
+#endif
+ PRIV_START
+ connectres = connect(s, (struct sockaddr *)&un, SUN_LEN(&un));
+ PRIV_END
+ if (connectres < 0) {
+ warn("Unable to connect to %s", _PATH_SOCKETNAME);
+ warnx("Check to see if the master 'lpd' process is running.");
+ (void) close(s);
+ return(0);
+ }
+
+ /*
+ * Avoid overruns without putting artificial limitations on
+ * the length.
+ */
+ if (writel(s, "\1", pp->printer, "\n", (char *)0) <= 0) {
+ warn("write");
+ (void) close(s);
+ return(0);
+ }
+ if (read(s, &c, 1) == 1) {
+ if (c == '\0') { /* everything is OK */
+ (void) close(s);
+ return(1);
+ }
+ putchar(c);
+ }
+ while ((n = read(s, &c, 1)) > 0)
+ putchar(c);
+ (void) close(s);
+ return(0);
+}
diff --git a/usr.sbin/lpr/filters.ru/Makefile b/usr.sbin/lpr/filters.ru/Makefile
new file mode 100644
index 000000000000..c100352d8af7
--- /dev/null
+++ b/usr.sbin/lpr/filters.ru/Makefile
@@ -0,0 +1,7 @@
+SUBDIR= koi2alt koi2855
+
+PACKAGE=lp
+FILES= bjc-240.sh.sample
+
+.include "Makefile.inc"
+.include <bsd.prog.mk>
diff --git a/usr.sbin/lpr/filters.ru/Makefile.depend b/usr.sbin/lpr/filters.ru/Makefile.depend
new file mode 100644
index 000000000000..11aba52f82cf
--- /dev/null
+++ b/usr.sbin/lpr/filters.ru/Makefile.depend
@@ -0,0 +1,10 @@
+# Autogenerated - do NOT edit!
+
+DIRDEPS = \
+
+
+.include <dirdeps.mk>
+
+.if ${DEP_RELDIR} == ${_DEP_RELDIR}
+# local dependencies - needed for -jN in clean tree
+.endif
diff --git a/usr.sbin/lpr/filters.ru/Makefile.inc b/usr.sbin/lpr/filters.ru/Makefile.inc
new file mode 100644
index 000000000000..2a311e5201db
--- /dev/null
+++ b/usr.sbin/lpr/filters.ru/Makefile.inc
@@ -0,0 +1,3 @@
+BINDIR= /usr/libexec/lpr/ru
+
+WARNS?= 3
diff --git a/usr.sbin/lpr/filters.ru/bjc-240.sh.sample b/usr.sbin/lpr/filters.ru/bjc-240.sh.sample
new file mode 100644
index 000000000000..1bc907a4202c
--- /dev/null
+++ b/usr.sbin/lpr/filters.ru/bjc-240.sh.sample
@@ -0,0 +1,64 @@
+#!/bin/sh
+#
+# Canon BJC-240 Setup script
+#
+# Settings are:
+# Epson LQ emulation, A4, Code Page 866, 66 lines, Roman font, Smoothing,
+# HQ mode, no CR translation, power off in 10min, auto power on
+#
+
+printf "\033[K\00200\037" | tr 0 "\0"
+
+cat << EOF1
+BJLSTART
+ControlMode=BJ
+Font=Roman
+PageLength=12
+CodePage=866
+AutoLF=Off
+TextScaleMode=On
+AutoCR=Off
+CharacterSet=Set2
+AGM=Off
+BJLEND
+EOF1
+
+printf "\033[K\00200\037" | tr 0 "\0"
+
+cat << EOF2
+BJLSTART
+ControlMode=LQ
+Font=Roman
+PageLength=12
+CodePage=866
+AutoLF=Off
+TextScaleMode=On
+CharacterSet=Graphics
+International=USA
+BJLEND
+EOF2
+
+printf "\033[K\00200\037" | tr 0 "\0"
+
+cat << EOF3
+BJLSTART
+@SetControlMode=LQ
+BJLEND
+EOF3
+
+printf "\033[K\00200\037" | tr 0 "\0"
+
+cat << EOF4
+BJLSTART
+ControlMode=Common
+PrintMode=HQ
+Reduction=Off
+Smoothing=On
+PaperSelect=A4
+I/D-Buffer=Input
+AutoPowerOff=10
+AutoPowerOn=Enable
+BJLEND
+EOF4
+
+exec /usr/libexec/lpr/ru/koi2alt $*
diff --git a/usr.sbin/lpr/filters.ru/koi2855/Makefile b/usr.sbin/lpr/filters.ru/koi2855/Makefile
new file mode 100644
index 000000000000..d551cce69094
--- /dev/null
+++ b/usr.sbin/lpr/filters.ru/koi2855/Makefile
@@ -0,0 +1,7 @@
+PACKAGE=lp
+PROG= koi2855
+MAN=
+
+CFLAGS+= -I${.CURDIR:H:H}/common_source
+
+.include <bsd.prog.mk>
diff --git a/usr.sbin/lpr/filters.ru/koi2855/Makefile.depend b/usr.sbin/lpr/filters.ru/koi2855/Makefile.depend
new file mode 100644
index 000000000000..93249906da4f
--- /dev/null
+++ b/usr.sbin/lpr/filters.ru/koi2855/Makefile.depend
@@ -0,0 +1,14 @@
+# Autogenerated - do NOT edit!
+
+DIRDEPS = \
+ include \
+ lib/${CSU_DIR} \
+ lib/libc \
+ lib/libcompiler_rt \
+
+
+.include <dirdeps.mk>
+
+.if ${DEP_RELDIR} == ${_DEP_RELDIR}
+# local dependencies - needed for -jN in clean tree
+.endif
diff --git a/usr.sbin/lpr/filters.ru/koi2855/koi2855.c b/usr.sbin/lpr/filters.ru/koi2855/koi2855.c
new file mode 100644
index 000000000000..595ea375a266
--- /dev/null
+++ b/usr.sbin/lpr/filters.ru/koi2855/koi2855.c
@@ -0,0 +1,104 @@
+/*-
+ * SPDX-License-Identifier: BSD-2-Clause
+ *
+ * Copyright (C) 1999 by Andrey A. Chernov, Moscow, Russia.
+ * (C) 18 Sep 1999, Alex G. Bulushev (bag@demos.su)
+ * All rights reserved.
+ *
+ * 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.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``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 "lp.cdefs.h" /* A cross-platform version of <sys/cdefs.h> */
+/*
+ * KOI8-R -> CP855 conversion filter (Russian character sets)
+ */
+
+#include <sys/types.h>
+#include <signal.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+
+int length = 66;
+int lines;
+
+unsigned char koi2855 [] = {
+0xc4, 0xb3, 0xda, 0xbf, 0xc0, 0xd9, 0xc3, 0xb4,
+0xc2, 0xc1, 0xc5, 0xdf, 0xdc, 0xdb, 0xdb, 0xdb,
+0xb0, 0xb1, 0xb2, 0xda, 0xfe, 0x2e, 0xad, 0x3d,
+0xae, 0xaf, 0xff, 0xd9, 0xcf, 0x32, 0x2e, 0x25,
+0xcd, 0xba, 0xc9, 0x84, 0xc9, 0xc9, 0xbb, 0xbb,
+0xbb, 0xc8, 0xc8, 0xc8, 0xbc, 0xbc, 0xbc, 0xcc,
+0xcc, 0xcc, 0xb9, 0x85, 0xb9, 0xb9, 0xcb, 0xcb,
+0xcb, 0xca, 0xca, 0xca, 0xce, 0xce, 0xce, 0x43,
+0x9c, 0xa0, 0xa2, 0xa4, 0xa6, 0xa8, 0xaa, 0xac,
+0xb5, 0xb7, 0xbd, 0xc6, 0xd0, 0xd2, 0xd4, 0xd6,
+0xd8, 0xde, 0xe1, 0xe3, 0xe5, 0xe7, 0xe9, 0xeb,
+0xed, 0xf1, 0xf3, 0xf5, 0xf7, 0xf9, 0xfb, 0x9e,
+0x9d, 0xa1, 0xa3, 0xa5, 0xa7, 0xa9, 0xab, 0xad,
+0xb6, 0xb8, 0xbe, 0xc7, 0xd1, 0xd3, 0xd5, 0xd7,
+0xdd, 0xe0, 0xe2, 0xe4, 0xe6, 0xe8, 0xea, 0xec,
+0xee, 0xf2, 0xf4, 0xf6, 0xf8, 0xfa, 0xfc, 0x9f
+};
+
+int main(int argc, char *argv[])
+{
+ int c, i;
+ char *cp;
+
+ while (--argc) {
+ if (*(cp = *++argv) == '-') {
+ switch (*++cp) {
+ case 'l':
+ if ((i = atoi(++cp)) > 0)
+ length = i;
+ break;
+ }
+ }
+ }
+
+ while ((c = getchar()) != EOF) {
+ if (c == '\031') {
+ if ((c = getchar()) == '\1') {
+ lines = 0;
+ fflush(stdout);
+ kill(getpid(), SIGSTOP);
+ continue;
+ } else {
+ ungetc(c, stdin);
+ c = '\031';
+ }
+ } else if (c & 0x80) {
+ putchar(koi2855[c & 0x7F]);
+ continue;
+ } else if (c == '\n')
+ lines++;
+ else if (c == '\f')
+ lines = length;
+ putchar(c);
+ if (lines >= length) {
+ lines = 0;
+ fflush(stdout);
+ }
+ }
+ return 0;
+}
diff --git a/usr.sbin/lpr/filters.ru/koi2alt/Makefile b/usr.sbin/lpr/filters.ru/koi2alt/Makefile
new file mode 100644
index 000000000000..5b3bfab3e93c
--- /dev/null
+++ b/usr.sbin/lpr/filters.ru/koi2alt/Makefile
@@ -0,0 +1,7 @@
+PACKAGE=lp
+PROG= koi2alt
+MAN=
+
+CFLAGS+= -I${.CURDIR:H:H}/common_source
+
+.include <bsd.prog.mk>
diff --git a/usr.sbin/lpr/filters.ru/koi2alt/Makefile.depend b/usr.sbin/lpr/filters.ru/koi2alt/Makefile.depend
new file mode 100644
index 000000000000..93249906da4f
--- /dev/null
+++ b/usr.sbin/lpr/filters.ru/koi2alt/Makefile.depend
@@ -0,0 +1,14 @@
+# Autogenerated - do NOT edit!
+
+DIRDEPS = \
+ include \
+ lib/${CSU_DIR} \
+ lib/libc \
+ lib/libcompiler_rt \
+
+
+.include <dirdeps.mk>
+
+.if ${DEP_RELDIR} == ${_DEP_RELDIR}
+# local dependencies - needed for -jN in clean tree
+.endif
diff --git a/usr.sbin/lpr/filters.ru/koi2alt/koi2alt.c b/usr.sbin/lpr/filters.ru/koi2alt/koi2alt.c
new file mode 100644
index 000000000000..25cf23e3a513
--- /dev/null
+++ b/usr.sbin/lpr/filters.ru/koi2alt/koi2alt.c
@@ -0,0 +1,105 @@
+/*-
+ * SPDX-License-Identifier: BSD-2-Clause
+ *
+ * Copyright (C) 1993-98 by Andrey A. Chernov, Moscow, Russia.
+ * All rights reserved.
+ *
+ * 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.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``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 "lp.cdefs.h" /* A cross-platform version of <sys/cdefs.h> */
+/*
+ * KOI8-R -> CP866 conversion filter (Russian character sets)
+ */
+
+#include <sys/types.h>
+#include <signal.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+
+int length = 66;
+int lines;
+
+char *koi2alt[] = {
+/* 0 1 2 3 4 5 6 7 */
+/* 8 9 A B C D E F */
+/* 8 */ "\xc4","\xb3","\xda","\xbf","\xc0","\xd9","\xc3","\xb4",
+ "\xc2","\xc1","\xc5","\xdf","\xdc","\xdb","\xdd","\xde",
+/* 9 */ "\xb0","\xb1","\xb2","\xb3","\xfe","\xf9","\xfb","-\b~",
+ "<\b_",">\b_","\xff","\xb3","\xf8","2\b-","\xfa",":\b-",
+/* A */ "\xcd","\xba","\xd5","\xf1","\xd6","\xc9","\xb8","\xb7",
+ "\xbb","\xd4","\xd3","\xc8","\xbe","\xbd","\xbc","\xc6",
+/* B */ "\xc7","\xcc","\xb5","\xf0","\xb6","\xb9","\xd1","\xd2",
+ "\xcb","\xcf","\xd0","\xca","\xd8","\xd7","\xce","c\b_",
+/* C */ "\xee","\xa0","\xa1","\xe6","\xa4","\xa5","\xe4","\xa3",
+ "\xe5","\xa8","\xa9","\xaa","\xab","\xac","\xad","\xae",
+/* D */ "\xaf","\xef","\xe0","\xe1","\xe2","\xe3","\xa6","\xa2",
+ "\xec","\xeb","\xa7","\xe8","\xed","\xe9","\xe7","\xea",
+/* E */ "\x9e","\x80","\x81","\x96","\x84","\x85","\x94","\x83",
+ "\x95","\x88","\x89","\x8a","\x8b","\x8c","\x8d","\x8e",
+/* F */ "\x8f","\x9f","\x90","\x91","\x92","\x93","\x86","\x82",
+ "\x9c","\x9b","\x87","\x98","\x9d","\x99","\x97","\x9a"
+};
+
+int main(int argc, char *argv[])
+{
+ int c, i;
+ char *cp;
+
+ while (--argc) {
+ if (*(cp = *++argv) == '-') {
+ switch (*++cp) {
+ case 'l':
+ if ((i = atoi(++cp)) > 0)
+ length = i;
+ break;
+ }
+ }
+ }
+
+ while ((c = getchar()) != EOF) {
+ if (c == '\031') {
+ if ((c = getchar()) == '\1') {
+ lines = 0;
+ fflush(stdout);
+ kill(getpid(), SIGSTOP);
+ continue;
+ } else {
+ ungetc(c, stdin);
+ c = '\031';
+ }
+ } else if (c & 0x80) {
+ fputs(koi2alt[c & 0x7F], stdout);
+ continue;
+ } else if (c == '\n')
+ lines++;
+ else if (c == '\f')
+ lines = length;
+ putchar(c);
+ if (lines >= length) {
+ lines = 0;
+ fflush(stdout);
+ }
+ }
+ return 0;
+}
diff --git a/usr.sbin/lpr/filters/Makefile b/usr.sbin/lpr/filters/Makefile
new file mode 100644
index 000000000000..3cca478a0e9b
--- /dev/null
+++ b/usr.sbin/lpr/filters/Makefile
@@ -0,0 +1,9 @@
+BINDIR= ${LIBEXECDIR}/lpr
+
+PACKAGE=lp
+PROG= lpf
+MAN=
+
+CFLAGS+= -I${.CURDIR:H}/common_source
+
+.include <bsd.prog.mk>
diff --git a/usr.sbin/lpr/filters/Makefile.depend b/usr.sbin/lpr/filters/Makefile.depend
new file mode 100644
index 000000000000..6ef78fac5cbf
--- /dev/null
+++ b/usr.sbin/lpr/filters/Makefile.depend
@@ -0,0 +1,15 @@
+# Autogenerated - do NOT edit!
+
+DIRDEPS = \
+ include \
+ include/xlocale \
+ lib/${CSU_DIR} \
+ lib/libc \
+ lib/libcompiler_rt \
+
+
+.include <dirdeps.mk>
+
+.if ${DEP_RELDIR} == ${_DEP_RELDIR}
+# local dependencies - needed for -jN in clean tree
+.endif
diff --git a/usr.sbin/lpr/filters/lpf.c b/usr.sbin/lpr/filters/lpf.c
new file mode 100644
index 000000000000..5a036de4fb6d
--- /dev/null
+++ b/usr.sbin/lpr/filters/lpf.c
@@ -0,0 +1,206 @@
+/*-
+ * SPDX-License-Identifier: BSD-3-Clause
+ *
+ * Copyright (c) 1983, 1993
+ * The Regents of the University of California. All rights reserved.
+ *
+ * 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 "lp.cdefs.h" /* A cross-platform version of <sys/cdefs.h> */
+/*
+ * filter which reads the output of nroff and converts lines
+ * with ^H's to overwritten lines. Thus this works like 'ul'
+ * but is much better: it can handle more than 2 overwrites
+ * and it is written with some style.
+ * modified by kls to use register references instead of arrays
+ * to try to gain a little speed.
+ */
+
+#include <signal.h>
+#include <unistd.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <string.h>
+
+#define MAXWIDTH 132
+#define MAXREP 10
+
+static char buf[MAXREP][MAXWIDTH];
+static int maxcol[MAXREP] = {-1};
+static int lineno;
+static int width = 132; /* default line length */
+static int length = 66; /* page length */
+static int indent; /* indentation length */
+static int npages = 1;
+static int literal; /* print control characters */
+static char *name; /* user's login name */
+static char *host; /* user's machine name */
+static char *acctfile; /* accounting information file */
+
+int
+main(int argc, char *argv[])
+{
+ register FILE *p = stdin, *o = stdout;
+ register int i, col;
+ register char *cp;
+ int done, linedone, maxrep;
+ char *limit;
+ int ch;
+
+ while (--argc) {
+ if (*(cp = *++argv) == '-') {
+ switch (cp[1]) {
+ case 'n':
+ argc--;
+ name = *++argv;
+ break;
+
+ case 'h':
+ argc--;
+ host = *++argv;
+ break;
+
+ case 'w':
+ if ((i = atoi(&cp[2])) > 0 && i <= MAXWIDTH)
+ width = i;
+ break;
+
+ case 'l':
+ length = atoi(&cp[2]);
+ break;
+
+ case 'i':
+ indent = atoi(&cp[2]);
+ break;
+
+ case 'c': /* Print control chars */
+ literal++;
+ break;
+ }
+ } else
+ acctfile = cp;
+ }
+
+ memset(buf, ' ', sizeof(buf));
+ done = 0;
+
+ while (!done) {
+ col = indent;
+ maxrep = -1;
+ linedone = 0;
+ while (!linedone) {
+ switch (ch = getc(p)) {
+ case EOF:
+ linedone = done = 1;
+ ch = '\n';
+ break;
+
+ case '\f':
+ lineno = length;
+ case '\n':
+ if (maxrep < 0)
+ maxrep = 0;
+ linedone = 1;
+ break;
+
+ case '\b':
+ if (--col < indent)
+ col = indent;
+ break;
+
+ case '\r':
+ col = indent;
+ break;
+
+ case '\t':
+ col = ((col - indent) | 07) + indent + 1;
+ break;
+
+ case '\031':
+ /*
+ * lpd needs to use a different filter to
+ * print data so stop what we are doing and
+ * wait for lpd to restart us.
+ */
+ if ((ch = getchar()) == '\1') {
+ fflush(stdout);
+ kill(getpid(), SIGSTOP);
+ break;
+ } else {
+ ungetc(ch, stdin);
+ ch = '\031';
+ }
+
+ default:
+ if (col >= width || (!literal && ch < ' ')) {
+ col++;
+ break;
+ }
+ cp = &buf[0][col];
+ for (i = 0; i < MAXREP; i++) {
+ if (i > maxrep)
+ maxrep = i;
+ if (*cp == ' ') {
+ *cp = ch;
+ if (col > maxcol[i])
+ maxcol[i] = col;
+ break;
+ }
+ cp += MAXWIDTH;
+ }
+ col++;
+ break;
+ }
+ }
+
+ /* print out lines */
+ for (i = 0; i <= maxrep; i++) {
+ for (cp = buf[i], limit = cp+maxcol[i]; cp <= limit;) {
+ putc(*cp, o);
+ *cp++ = ' ';
+ }
+ if (i < maxrep)
+ putc('\r', o);
+ else
+ putc(ch, o);
+ if (++lineno >= length) {
+ fflush(o);
+ npages++;
+ lineno = 0;
+ }
+ maxcol[i] = -1;
+ }
+ }
+ if (lineno) { /* be sure to end on a page boundary */
+ putchar('\f');
+ npages++;
+ }
+ if (name && acctfile && access(acctfile, 02) >= 0 &&
+ freopen(acctfile, "a", stdout) != NULL) {
+ printf("%7.2f\t%s:%s\n", (float)npages, host, name);
+ }
+ exit(0);
+}
diff --git a/usr.sbin/lpr/lp/Makefile b/usr.sbin/lpr/lp/Makefile
new file mode 100644
index 000000000000..3edd8023c89a
--- /dev/null
+++ b/usr.sbin/lpr/lp/Makefile
@@ -0,0 +1,7 @@
+BINDIR= /usr/bin
+
+PACKAGE=lp
+SCRIPTS=lp.sh
+MAN= lp.1
+
+.include <bsd.prog.mk>
diff --git a/usr.sbin/lpr/lp/Makefile.depend b/usr.sbin/lpr/lp/Makefile.depend
new file mode 100644
index 000000000000..11aba52f82cf
--- /dev/null
+++ b/usr.sbin/lpr/lp/Makefile.depend
@@ -0,0 +1,10 @@
+# Autogenerated - do NOT edit!
+
+DIRDEPS = \
+
+
+.include <dirdeps.mk>
+
+.if ${DEP_RELDIR} == ${_DEP_RELDIR}
+# local dependencies - needed for -jN in clean tree
+.endif
diff --git a/usr.sbin/lpr/lp/lp.1 b/usr.sbin/lpr/lp/lp.1
new file mode 100644
index 000000000000..907ce867279e
--- /dev/null
+++ b/usr.sbin/lpr/lp/lp.1
@@ -0,0 +1,123 @@
+.\"
+.\" Copyright (c) 1995 Joerg Wunsch
+.\"
+.\" All rights reserved.
+.\"
+.\" This program is free software.
+.\"
+.\" 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. All advertising materials mentioning features or use of this software
+.\" must display the following acknowledgement:
+.\" This product includes software developed by Joerg Wunsch
+.\" 4. The name of the developer may not be used to endorse or promote
+.\" products derived from this software without specific prior written
+.\" permission.
+.\"
+.\" THIS SOFTWARE IS PROVIDED BY THE DEVELOPERS ``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 DEVELOPERS 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.
+.\"
+.Dd January 22, 1995
+.Dt LP 1
+.Os
+.Sh NAME
+.Nm lp
+.Nd front-end to the print spooler
+.Sh SYNOPSIS
+.Nm
+.Op Fl cs
+.Op Fl o Ar option
+.Op Fl d Ar printer
+.Op Fl n Ar num
+.Op Ar name ...
+.Sh DESCRIPTION
+The
+.Nm
+utility is a front-end to the print spooler as required by the
+.St -p1003.2
+specification.
+It effectively invokes
+.Xr lpr 1
+with the proper set of arguments.
+.Pp
+It generally prints the named files on the destination printer.
+.Pp
+The following options are available:
+.Bl -tag -width indent
+.It Fl c
+Make the
+.Nm
+command exit only after further access to any of the input files is no
+longer required.
+The application can then safely delete or modify the
+files without affecting the output operation.
+.It Fl d Ar dest
+Specify a particular printer.
+If no
+.Fl d
+is provided on the command line, the contents of the environment
+variables
+.Ev LPDEST
+or
+.Ev PRINTER
+(with this precedence)
+are taken as the destination printer.
+.It Fl m
+Send mail upon completion.
+.It Fl n Ar num
+Specify that
+.Ar num
+copies of each of the named files shall be printed.
+.It Fl o Ar option
+Printer specific options.
+Not supported, provided only as a compatibility
+option for SVR.
+.It Fl s
+Silent operation.
+Not supported,
+provided only as a compatibility option for
+.St -susv2 .
+.It Fl t Ar title
+Set the job title to
+.Ar title .
+.El
+.Sh ENVIRONMENT
+As described above, the variables
+.Ev LPDEST
+and
+.Ev PRINTER
+are examined to select the destination printer.
+.Sh SEE ALSO
+.Xr lpr 1
+.Sh STANDARDS
+The
+.Nm
+command is expected to comply with the
+.St -p1003.2
+specification.
+.Sh AUTHORS
+This implementation of the
+.Nm
+command has been written by
+.An J\(:org Wunsch .
+.Sh BUGS
+The
+.St -p1003.2
+specification does not provide any means to print non-text files.
+It
+rather requires the files to be printed to be text files limited to
+reasonable line lengths and printable characters.
diff --git a/usr.sbin/lpr/lp/lp.sh b/usr.sbin/lpr/lp/lp.sh
new file mode 100644
index 000000000000..114b8e579ebc
--- /dev/null
+++ b/usr.sbin/lpr/lp/lp.sh
@@ -0,0 +1,82 @@
+#!/bin/sh
+#
+#
+# SPDX-License-Identifier: BSD-4-Clause
+#
+# Copyright (c) 1995 Joerg Wunsch
+#
+# All rights reserved.
+#
+# This program is free software.
+#
+# 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. All advertising materials mentioning features or use of this software
+# must display the following acknowledgement:
+# This product includes software developed by Joerg Wunsch
+# 4. The name of the developer may not be used to endorse or promote
+# products derived from this software without specific prior written
+# permission.
+#
+# THIS SOFTWARE IS PROVIDED BY THE DEVELOPERS ``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 DEVELOPERS 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.
+#
+#
+# Posix 1003.2 compliant print spooler interface.
+#
+#
+
+ncopies=""
+symlink="-s"
+mailafter=""
+title=""
+
+# Posix says LPDEST gets precedence over PRINTER
+dest=${LPDEST:-${PRINTER:-lp}}
+
+#
+# XXX We include the -o flag as a dummy. Posix 1003.2 does not require
+# it, but the rationale mentions it as a possible future extension.
+# XXX We include the -s flag as a dummy. SUSv2 requires it,
+# although we do not yet emit the affected messages.
+#
+while getopts "cd:mn:o:st:" option
+do
+ case $option in
+
+ c) # copy files before printing
+ symlink="";;
+ d) # destination
+ dest="${OPTARG}";;
+ m) # mail after job
+ mailafter="-m";;
+ n) # number of copies
+ ncopies="-#${OPTARG}";;
+ o) # (printer option)
+ : ;;
+ s) # (silent option)
+ : ;;
+ t) # title for banner page
+ title="${OPTARG}";;
+ *) # (error msg printed by getopts)
+ exit 2;;
+ esac
+done
+
+shift $(($OPTIND - 1))
+
+exec /usr/bin/lpr "-P${dest}" ${symlink} ${ncopies} ${mailafter} ${title:+-J"${title}"} "$@"
diff --git a/usr.sbin/lpr/lpc/Makefile b/usr.sbin/lpr/lpc/Makefile
new file mode 100644
index 000000000000..ca9dd2531fb2
--- /dev/null
+++ b/usr.sbin/lpr/lpc/Makefile
@@ -0,0 +1,16 @@
+.PATH: ${.CURDIR:H}/common_source
+
+PACKAGE=lp
+PROG= lpc
+MAN= lpc.8
+SRCS= lpc.c cmds.c cmdtab.c movejobs.c
+BINGRP= daemon
+BINMODE= 2555
+
+CFLAGS+= -I${.CURDIR:H}/common_source
+
+WARNS?= 0
+
+LIBADD= lpr edit
+
+.include <bsd.prog.mk>
diff --git a/usr.sbin/lpr/lpc/Makefile.depend b/usr.sbin/lpr/lpc/Makefile.depend
new file mode 100644
index 000000000000..50679f3178f8
--- /dev/null
+++ b/usr.sbin/lpr/lpc/Makefile.depend
@@ -0,0 +1,17 @@
+# Autogenerated - do NOT edit!
+
+DIRDEPS = \
+ include \
+ include/xlocale \
+ lib/${CSU_DIR} \
+ lib/libc \
+ lib/libcompiler_rt \
+ lib/libedit \
+ usr.sbin/lpr/common_source \
+
+
+.include <dirdeps.mk>
+
+.if ${DEP_RELDIR} == ${_DEP_RELDIR}
+# local dependencies - needed for -jN in clean tree
+.endif
diff --git a/usr.sbin/lpr/lpc/cmds.c b/usr.sbin/lpr/lpc/cmds.c
new file mode 100644
index 000000000000..30e091577732
--- /dev/null
+++ b/usr.sbin/lpr/lpc/cmds.c
@@ -0,0 +1,1314 @@
+/*-
+ * SPDX-License-Identifier: BSD-3-Clause
+ *
+ * Copyright (c) 1983, 1993
+ * The Regents of the University of California. All rights reserved.
+ *
+ *
+ * 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 "lp.cdefs.h" /* A cross-platform version of <sys/cdefs.h> */
+/*
+ * lpc -- line printer control program -- commands:
+ */
+
+#include <sys/param.h>
+#include <sys/time.h>
+#include <sys/stat.h>
+#include <sys/file.h>
+
+#include <signal.h>
+#include <fcntl.h>
+#include <err.h>
+#include <errno.h>
+#include <dirent.h>
+#include <unistd.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <ctype.h>
+#include <string.h>
+#include "lp.h"
+#include "lp.local.h"
+#include "lpc.h"
+#include "extern.h"
+#include "pathnames.h"
+
+/*
+ * Return values from kill_qtask().
+ */
+#define KQT_LFERROR -2
+#define KQT_KILLFAIL -1
+#define KQT_NODAEMON 0
+#define KQT_KILLOK 1
+
+static char *args2line(int argc, char **argv);
+static int doarg(char *_job);
+static int doselect(const struct dirent *_d);
+static int kill_qtask(const char *lf);
+static int sortq(const struct dirent **a, const struct dirent **b);
+static int touch(struct jobqueue *_jq);
+static void unlinkf(char *_name);
+static void upstat(struct printer *_pp, const char *_msg, int _notify);
+static void wrapup_clean(int _laststatus);
+
+/*
+ * generic framework for commands which operate on all or a specified
+ * set of printers
+ */
+enum qsel_val { /* how a given ptr was selected */
+ QSEL_UNKNOWN = -1, /* ... not selected yet */
+ QSEL_BYNAME = 0, /* ... user specified it by name */
+ QSEL_ALL = 1 /* ... user wants "all" printers */
+ /* (with more to come) */
+};
+
+static enum qsel_val generic_qselect; /* indicates how ptr was selected */
+static int generic_initerr; /* result of initrtn processing */
+static char *generic_cmdname;
+static char *generic_msg; /* if a -msg was specified */
+static char *generic_nullarg;
+static void (*generic_wrapup)(int _last_status); /* perform rtn wrap-up */
+
+void
+generic(void (*specificrtn)(struct printer *_pp), int cmdopts,
+ void (*initrtn)(int _argc, char *_argv[]), int argc, char *argv[])
+{
+ int cmdstatus, more, targc;
+ struct printer myprinter, *pp;
+ char **margv, **targv;
+
+ if (argc == 1) {
+ /*
+ * Usage needs a special case for 'down': The user must
+ * either include `-msg', or only the first parameter
+ * that they give will be processed as a printer name.
+ */
+ printf("usage: %s {all | printer ...}", argv[0]);
+ if (strcmp(argv[0], "down") == 0) {
+ printf(" -msg [<text> ...]\n");
+ printf(" or: down {all | printer} [<text> ...]");
+ } else if (cmdopts & LPC_MSGOPT)
+ printf(" [-msg <text> ...]");
+ printf("\n");
+ return;
+ }
+
+ /* The first argument is the command name. */
+ generic_cmdname = *argv++;
+ argc--;
+
+ /*
+ * The initialization routine for a command might set a generic
+ * "wrapup" routine, which should be called after processing all
+ * the printers in the command. This might print summary info.
+ *
+ * Note that the initialization routine may also parse (and
+ * nullify) some of the parameters given on the command, leaving
+ * only the parameters which have to do with printer names.
+ */
+ pp = &myprinter;
+ generic_wrapup = NULL;
+ generic_qselect = QSEL_UNKNOWN;
+ cmdstatus = 0;
+ /* this just needs to be a distinct value of type 'char *' */
+ if (generic_nullarg == NULL)
+ generic_nullarg = strdup("");
+
+ /*
+ * Some commands accept a -msg argument, which indicates that
+ * all remaining arguments should be combined into a string.
+ */
+ generic_msg = NULL;
+ if (cmdopts & LPC_MSGOPT) {
+ targc = argc;
+ targv = argv;
+ for (; targc > 0; targc--, targv++) {
+ if (strcmp(*targv, "-msg") == 0) {
+ argc -= targc;
+ generic_msg = args2line(targc - 1, targv + 1);
+ break;
+ }
+ }
+ if (argc < 1) {
+ printf("error: No printer name(s) specified before"
+ " '-msg'.\n");
+ printf("usage: %s {all | printer ...}",
+ generic_cmdname);
+ printf(" [-msg <text> ...]\n");
+ return;
+ }
+ }
+
+ /* call initialization routine, if there is one for this cmd */
+ if (initrtn != NULL) {
+ generic_initerr = 0;
+ (*initrtn)(argc, argv);
+ if (generic_initerr)
+ return;
+ /*
+ * The initrtn may have null'ed out some of the parameters.
+ * Compact the parameter list to remove those nulls, and
+ * correct the arg-count.
+ */
+ targc = argc;
+ targv = argv;
+ margv = argv;
+ argc = 0;
+ for (; targc > 0; targc--, targv++) {
+ if (*targv != generic_nullarg) {
+ if (targv != margv)
+ *margv = *targv;
+ margv++;
+ argc++;
+ }
+ }
+ }
+
+ if (argc == 1 && strcmp(*argv, "all") == 0) {
+ generic_qselect = QSEL_ALL;
+ more = firstprinter(pp, &cmdstatus);
+ if (cmdstatus)
+ goto looperr;
+ while (more) {
+ (*specificrtn)(pp);
+ do {
+ more = nextprinter(pp, &cmdstatus);
+looperr:
+ switch (cmdstatus) {
+ case PCAPERR_TCOPEN:
+ printf("warning: %s: unresolved "
+ "tc= reference(s) ",
+ pp->printer);
+ case PCAPERR_SUCCESS:
+ break;
+ default:
+ fatal(pp, "%s", pcaperr(cmdstatus));
+ }
+ } while (more && cmdstatus);
+ }
+ goto wrapup;
+ }
+
+ generic_qselect = QSEL_BYNAME; /* specifically-named ptrs */
+ for (; argc > 0; argc--, argv++) {
+ init_printer(pp);
+ cmdstatus = getprintcap(*argv, pp);
+ switch (cmdstatus) {
+ default:
+ fatal(pp, "%s", pcaperr(cmdstatus));
+ case PCAPERR_NOTFOUND:
+ printf("unknown printer %s\n", *argv);
+ continue;
+ case PCAPERR_TCOPEN:
+ printf("warning: %s: unresolved tc= reference(s)\n",
+ *argv);
+ break;
+ case PCAPERR_SUCCESS:
+ break;
+ }
+ (*specificrtn)(pp);
+ }
+
+wrapup:
+ if (generic_wrapup) {
+ (*generic_wrapup)(cmdstatus);
+ }
+ free_printer(pp);
+ if (generic_msg)
+ free(generic_msg);
+}
+
+/*
+ * Convert an argv-array of character strings into a single string.
+ */
+static char *
+args2line(int argc, char **argv)
+{
+ char *cp1, *cend;
+ const char *cp2;
+ char buf[1024];
+
+ if (argc <= 0)
+ return strdup("\n");
+
+ cp1 = buf;
+ cend = buf + sizeof(buf) - 1; /* save room for '\0' */
+ while (--argc >= 0) {
+ cp2 = *argv++;
+ while ((cp1 < cend) && (*cp1++ = *cp2++))
+ ;
+ cp1[-1] = ' ';
+ }
+ cp1[-1] = '\n';
+ *cp1 = '\0';
+ return strdup(buf);
+}
+
+/*
+ * Kill the current daemon, to stop printing of the active job.
+ */
+static int
+kill_qtask(const char *lf)
+{
+ FILE *fp;
+ pid_t pid;
+ int errsav, killres, lockres, res;
+
+ PRIV_START
+ fp = fopen(lf, "r");
+ errsav = errno;
+ PRIV_END
+ res = KQT_NODAEMON;
+ if (fp == NULL) {
+ /*
+ * If there is no lock file, then there is no daemon to
+ * kill. Any other error return means there is some
+ * kind of problem with the lock file.
+ */
+ if (errsav != ENOENT)
+ res = KQT_LFERROR;
+ goto killdone;
+ }
+
+ /* If the lock file is empty, then there is no daemon to kill */
+ if (get_line(fp) == 0)
+ goto killdone;
+
+ /*
+ * If the file can be locked without blocking, then there
+ * no daemon to kill, or we should not try to kill it.
+ *
+ * XXX - not sure I understand the reasoning behind this...
+ */
+ lockres = flock(fileno(fp), LOCK_SH|LOCK_NB);
+ (void) fclose(fp);
+ if (lockres == 0)
+ goto killdone;
+
+ pid = atoi(line);
+ if (pid < 0) {
+ /*
+ * If we got a negative pid, then the contents of the
+ * lock file is not valid.
+ */
+ res = KQT_LFERROR;
+ goto killdone;
+ }
+
+ PRIV_END
+ killres = kill(pid, SIGTERM);
+ errsav = errno;
+ PRIV_END
+ if (killres == 0) {
+ res = KQT_KILLOK;
+ printf("\tdaemon (pid %d) killed\n", pid);
+ } else if (errno == ESRCH) {
+ res = KQT_NODAEMON;
+ } else {
+ res = KQT_KILLFAIL;
+ printf("\tWarning: daemon (pid %d) not killed:\n", pid);
+ printf("\t %s\n", strerror(errsav));
+ }
+
+killdone:
+ switch (res) {
+ case KQT_LFERROR:
+ printf("\tcannot open lock file: %s\n",
+ strerror(errsav));
+ break;
+ case KQT_NODAEMON:
+ printf("\tno daemon to abort\n");
+ break;
+ case KQT_KILLFAIL:
+ case KQT_KILLOK:
+ /* These two already printed messages to the user. */
+ break;
+ default:
+ printf("\t<internal error in kill_qtask>\n");
+ break;
+ }
+
+ return (res);
+}
+
+/*
+ * Write a message into the status file.
+ */
+static void
+upstat(struct printer *pp, const char *msg, int notifyuser)
+{
+ int fd;
+ char statfile[MAXPATHLEN];
+
+ status_file_name(pp, statfile, sizeof statfile);
+ umask(0);
+ PRIV_START
+ fd = open(statfile, O_WRONLY|O_CREAT|O_EXLOCK, STAT_FILE_MODE);
+ PRIV_END
+ if (fd < 0) {
+ printf("\tcannot create status file: %s\n", strerror(errno));
+ return;
+ }
+ (void) ftruncate(fd, 0);
+ if (msg == NULL)
+ (void) write(fd, "\n", 1);
+ else
+ (void) write(fd, msg, strlen(msg));
+ (void) close(fd);
+ if (notifyuser) {
+ if ((msg == (char *)NULL) || (strcmp(msg, "\n") == 0))
+ printf("\tstatus message is now set to nothing.\n");
+ else
+ printf("\tstatus message is now: %s", msg);
+ }
+}
+
+/*
+ * kill an existing daemon and disable printing.
+ */
+void
+abort_q(struct printer *pp)
+{
+ int killres, setres;
+ char lf[MAXPATHLEN];
+
+ lock_file_name(pp, lf, sizeof lf);
+ printf("%s:\n", pp->printer);
+
+ /*
+ * Turn on the owner execute bit of the lock file to disable printing.
+ */
+ setres = set_qstate(SQS_STOPP, lf);
+
+ /*
+ * If set_qstate found that there already was a lock file, then
+ * call a routine which will read that lock file and kill the
+ * lpd-process which is listed in that lock file. If the lock
+ * file did not exist, then either there is no daemon running
+ * for this queue, or there is one running but *it* could not
+ * write a lock file (which means we can not determine the
+ * process id of that lpd-process).
+ */
+ switch (setres) {
+ case SQS_CHGOK:
+ case SQS_CHGFAIL:
+ /* Kill the process */
+ killres = kill_qtask(lf);
+ break;
+ case SQS_CREOK:
+ case SQS_CREFAIL:
+ printf("\tno daemon to abort\n");
+ break;
+ case SQS_STATFAIL:
+ printf("\tassuming no daemon to abort\n");
+ break;
+ default:
+ printf("\t<unexpected result (%d) from set_qstate>\n",
+ setres);
+ break;
+ }
+
+ if (setres >= 0)
+ upstat(pp, "printing disabled\n", 0);
+}
+
+/*
+ * "global" variables for all the routines related to 'clean' and 'tclean'
+ */
+static time_t cln_now; /* current time */
+static double cln_minage; /* minimum age before file is removed */
+static long cln_sizecnt; /* amount of space freed up */
+static int cln_debug; /* print extra debugging msgs */
+static int cln_filecnt; /* number of files destroyed */
+static int cln_foundcore; /* found a core file! */
+static int cln_queuecnt; /* number of queues checked */
+static int cln_testonly; /* remove-files vs just-print-info */
+
+static int
+doselect(const struct dirent *d)
+{
+ int c = d->d_name[0];
+
+ if ((c == 'c' || c == 'd' || c == 'r' || c == 't') &&
+ d->d_name[1] == 'f')
+ return 1;
+ if (c == 'c') {
+ if (!strcmp(d->d_name, "core"))
+ cln_foundcore = 1;
+ }
+ if (c == 'e') {
+ if (!strncmp(d->d_name, "errs.", 5))
+ return 1;
+ }
+ return 0;
+}
+
+/*
+ * Comparison routine that clean_q() uses for scandir.
+ *
+ * The purpose of this sort is to have all `df' files end up immediately
+ * after the matching `cf' file. For files matching `cf', `df', `rf', or
+ * `tf', it sorts by job number and machine, then by `cf', `df', `rf', or
+ * `tf', and then by the sequence letter (which is A-Z, or a-z). This
+ * routine may also see filenames which do not start with `cf', `df', `rf',
+ * or `tf' (such as `errs.*'), and those are simply sorted by the full
+ * filename.
+ *
+ * XXX
+ * This assumes that all control files start with `cfA*', and it turns
+ * out there are a few implementations of lpr which will create `cfB*'
+ * filenames (they will have datafile names which start with `dfB*').
+ */
+static int
+sortq(const struct dirent **a, const struct dirent **b)
+{
+ const int a_lt_b = -1, a_gt_b = 1, cat_other = 10;
+ const char *fname_a, *fname_b, *jnum_a, *jnum_b;
+ int cat_a, cat_b, ch, res, seq_a, seq_b;
+
+ fname_a = (*a)->d_name;
+ fname_b = (*b)->d_name;
+
+ /*
+ * First separate filenames into categories. Categories are
+ * legitimate `cf', `df', `rf' & `tf' filenames, and "other" - in
+ * that order. It is critical that the mapping be exactly the
+ * same for 'a' vs 'b', so define a macro for the job.
+ *
+ * [aside: the standard `cf' file has the jobnumber start in
+ * position 4, but some implementations have that as an extra
+ * file-sequence letter, and start the job number in position 5.]
+ */
+#define MAP_TO_CAT(fname_X,cat_X,jnum_X,seq_X) do { \
+ cat_X = cat_other; \
+ ch = *(fname_X + 2); \
+ jnum_X = fname_X + 3; \
+ seq_X = 0; \
+ if ((*(fname_X + 1) == 'f') && (isalpha(ch))) { \
+ seq_X = ch; \
+ if (*fname_X == 'c') \
+ cat_X = 1; \
+ else if (*fname_X == 'd') \
+ cat_X = 2; \
+ else if (*fname_X == 'r') \
+ cat_X = 3; \
+ else if (*fname_X == 't') \
+ cat_X = 4; \
+ if (cat_X != cat_other) { \
+ ch = *jnum_X; \
+ if (!isdigit(ch)) { \
+ if (isalpha(ch)) { \
+ jnum_X++; \
+ ch = *jnum_X; \
+ seq_X = (seq_X << 8) + ch; \
+ } \
+ if (!isdigit(ch)) \
+ cat_X = cat_other; \
+ } \
+ } \
+ } \
+} while (0)
+
+ MAP_TO_CAT(fname_a, cat_a, jnum_a, seq_a);
+ MAP_TO_CAT(fname_b, cat_b, jnum_b, seq_b);
+
+#undef MAP_TO_CAT
+
+ /* First handle all cases which have "other" files */
+ if ((cat_a >= cat_other) || (cat_b >= cat_other)) {
+ /* for two "other" files, just compare the full name */
+ if (cat_a == cat_b)
+ res = strcmp(fname_a, fname_b);
+ else if (cat_a < cat_b)
+ res = a_lt_b;
+ else
+ res = a_gt_b;
+ goto have_res;
+ }
+
+ /*
+ * At this point, we know both files are legitimate `cf', `df', `rf',
+ * or `tf' files. Compare them by job-number and machine name.
+ */
+ res = strcmp(jnum_a, jnum_b);
+ if (res != 0)
+ goto have_res;
+
+ /*
+ * We have two files which belong to the same job. Sort based
+ * on the category of file (`c' before `d', etc).
+ */
+ if (cat_a < cat_b) {
+ res = a_lt_b;
+ goto have_res;
+ } else if (cat_a > cat_b) {
+ res = a_gt_b;
+ goto have_res;
+ }
+
+ /*
+ * Two files in the same category for a single job. Sort based
+ * on the sequence letter(s). (usually `A' through `Z', etc).
+ */
+ if (seq_a < seq_b) {
+ res = a_lt_b;
+ goto have_res;
+ } else if (seq_a > seq_b) {
+ res = a_gt_b;
+ goto have_res;
+ }
+
+ /*
+ * Given that the filenames in a directory are unique, this SHOULD
+ * never happen (unless there are logic errors in this routine).
+ * But if it does happen, we must return "is equal" or the caller
+ * might see inconsistent results in the sorting order, and that
+ * can trigger other problems.
+ */
+ printf("\t*** Error in sortq: %s == %s !\n", fname_a, fname_b);
+ printf("\t*** cat %d == %d ; seq = %d %d\n", cat_a, cat_b,
+ seq_a, seq_b);
+ res = 0;
+
+have_res:
+ return res;
+}
+
+/*
+ * Remove all spool files and temporaries from the spooling area.
+ * Or, perhaps:
+ * Remove incomplete jobs from spooling area.
+ */
+
+void
+clean_gi(int argc, char *argv[])
+{
+
+ /* init some fields before 'clean' is called for each queue */
+ cln_queuecnt = 0;
+ cln_now = time(NULL);
+ cln_minage = 3600.0; /* only delete files >1h old */
+ cln_filecnt = 0;
+ cln_sizecnt = 0;
+ cln_debug = 0;
+ cln_testonly = 0;
+ generic_wrapup = &wrapup_clean;
+
+ /* see if there are any options specified before the ptr list */
+ for (; argc > 0; argc--, argv++) {
+ if (**argv != '-')
+ break;
+ if (strcmp(*argv, "-d") == 0) {
+ /* just an example of an option... */
+ cln_debug++;
+ *argv = generic_nullarg; /* "erase" it */
+ } else {
+ printf("Invalid option '%s'\n", *argv);
+ generic_initerr = 1;
+ }
+ }
+}
+
+void
+tclean_gi(int argc, char *argv[])
+{
+
+ /* only difference between 'clean' and 'tclean' is one value */
+ /* (...and the fact that 'clean' is priv and 'tclean' is not) */
+ clean_gi(argc, argv);
+ cln_testonly = 1;
+}
+
+void
+clean_q(struct printer *pp)
+{
+ char *cp, *cp1, *lp;
+ struct dirent **queue;
+ size_t linerem;
+ int didhead, i, n, nitems, rmcp;
+
+ cln_queuecnt++;
+
+ didhead = 0;
+ if (generic_qselect == QSEL_BYNAME) {
+ printf("%s:\n", pp->printer);
+ didhead = 1;
+ }
+
+ lp = line;
+ cp = pp->spool_dir;
+ while (lp < &line[sizeof(line) - 1]) {
+ if ((*lp++ = *cp++) == 0)
+ break;
+ }
+ lp[-1] = '/';
+ linerem = sizeof(line) - (lp - line);
+
+ cln_foundcore = 0;
+ PRIV_START
+ nitems = scandir(pp->spool_dir, &queue, doselect, sortq);
+ PRIV_END
+ if (nitems < 0) {
+ if (!didhead) {
+ printf("%s:\n", pp->printer);
+ didhead = 1;
+ }
+ printf("\tcannot examine spool directory\n");
+ return;
+ }
+ if (cln_foundcore) {
+ if (!didhead) {
+ printf("%s:\n", pp->printer);
+ didhead = 1;
+ }
+ printf("\t** found a core file in %s !\n", pp->spool_dir);
+ }
+ if (nitems == 0)
+ return;
+ if (!didhead)
+ printf("%s:\n", pp->printer);
+ if (cln_debug) {
+ printf("\t** ----- Sorted list of files being checked:\n");
+ i = 0;
+ do {
+ cp = queue[i]->d_name;
+ printf("\t** [%3d] = %s\n", i, cp);
+ } while (++i < nitems);
+ printf("\t** ----- end of sorted list\n");
+ }
+ i = 0;
+ do {
+ cp = queue[i]->d_name;
+ rmcp = 0;
+ if (*cp == 'c') {
+ /*
+ * A control file. Look for matching data-files.
+ */
+ /* XXX
+ * Note the logic here assumes that the hostname
+ * part of cf-filenames match the hostname part
+ * in df-filenames, and that is not necessarily
+ * true (eg: for multi-homed hosts). This needs
+ * some further thought...
+ */
+ n = 0;
+ while (i + 1 < nitems) {
+ cp1 = queue[i + 1]->d_name;
+ if (*cp1 != 'd' || strcmp(cp + 3, cp1 + 3))
+ break;
+ i++;
+ n++;
+ }
+ if (n == 0) {
+ rmcp = 1;
+ }
+ } else if (*cp == 'e') {
+ /*
+ * Must be an errrs or email temp file.
+ */
+ rmcp = 1;
+ } else {
+ /*
+ * Must be a df with no cf (otherwise, it would have
+ * been skipped above) or an rf or tf file (which can
+ * always be removed if it is old enough).
+ */
+ rmcp = 1;
+ }
+ if (rmcp) {
+ if (strlen(cp) >= linerem) {
+ printf("\t** internal error: 'line' overflow!\n");
+ printf("\t** spooldir = %s\n", pp->spool_dir);
+ printf("\t** cp = %s\n", cp);
+ return;
+ }
+ strlcpy(lp, cp, linerem);
+ unlinkf(line);
+ }
+ } while (++i < nitems);
+}
+
+static void
+wrapup_clean(int laststatus __unused)
+{
+
+ printf("Checked %d queues, and ", cln_queuecnt);
+ if (cln_filecnt < 1) {
+ printf("no cruft was found\n");
+ return;
+ }
+ if (cln_testonly) {
+ printf("would have ");
+ }
+ printf("removed %d files (%ld bytes).\n", cln_filecnt, cln_sizecnt);
+}
+
+static void
+unlinkf(char *name)
+{
+ struct stat stbuf;
+ double agemod, agestat;
+ int res;
+ char linkbuf[BUFSIZ];
+
+ /*
+ * We have to use lstat() instead of stat(), in case this is a df*
+ * "file" which is really a symlink due to 'lpr -s' processing. In
+ * that case, we need to check the last-mod time of the symlink, and
+ * not the file that the symlink is pointed at.
+ */
+ PRIV_START
+ res = lstat(name, &stbuf);
+ PRIV_END
+ if (res < 0) {
+ printf("\terror return from stat(%s):\n", name);
+ printf("\t %s\n", strerror(errno));
+ return;
+ }
+
+ agemod = difftime(cln_now, stbuf.st_mtime);
+ agestat = difftime(cln_now, stbuf.st_ctime);
+ if (cln_debug > 1) {
+ /* this debugging-aid probably is not needed any more... */
+ printf("\t\t modify age=%g secs, stat age=%g secs\n",
+ agemod, agestat);
+ }
+ if ((agemod <= cln_minage) && (agestat <= cln_minage))
+ return;
+
+ /*
+ * if this file is a symlink, then find out the target of the
+ * symlink before unlink-ing the file itself
+ */
+ if (S_ISLNK(stbuf.st_mode)) {
+ PRIV_START
+ res = readlink(name, linkbuf, sizeof(linkbuf));
+ PRIV_END
+ if (res < 0) {
+ printf("\terror return from readlink(%s):\n", name);
+ printf("\t %s\n", strerror(errno));
+ return;
+ }
+ if (res == sizeof(linkbuf))
+ res--;
+ linkbuf[res] = '\0';
+ }
+
+ cln_filecnt++;
+ cln_sizecnt += stbuf.st_size;
+
+ if (cln_testonly) {
+ printf("\twould remove %s\n", name);
+ if (S_ISLNK(stbuf.st_mode)) {
+ printf("\t (which is a symlink to %s)\n", linkbuf);
+ }
+ } else {
+ PRIV_START
+ res = unlink(name);
+ PRIV_END
+ if (res < 0)
+ printf("\tcannot remove %s (!)\n", name);
+ else
+ printf("\tremoved %s\n", name);
+ /* XXX
+ * Note that for a df* file, this code should also check to see
+ * if it is a symlink to some other file, and if the original
+ * lpr command included '-r' ("remove file"). Of course, this
+ * code would not be removing the df* file unless there was no
+ * matching cf* file, and without the cf* file it is currently
+ * impossible to determine if '-r' had been specified...
+ *
+ * As a result of this quandry, we may be leaving behind a
+ * user's file that was supposed to have been removed after
+ * being printed. This may effect services such as CAP or
+ * samba, if they were configured to use 'lpr -r', and if
+ * datafiles are not being properly removed.
+ */
+ if (S_ISLNK(stbuf.st_mode)) {
+ printf("\t (which was a symlink to %s)\n", linkbuf);
+ }
+ }
+}
+
+/*
+ * Enable queuing to the printer (allow lpr to add new jobs to the queue).
+ */
+void
+enable_q(struct printer *pp)
+{
+ int setres;
+ char lf[MAXPATHLEN];
+
+ lock_file_name(pp, lf, sizeof lf);
+ printf("%s:\n", pp->printer);
+
+ setres = set_qstate(SQS_ENABLEQ, lf);
+}
+
+/*
+ * Disable queuing.
+ */
+void
+disable_q(struct printer *pp)
+{
+ int setres;
+ char lf[MAXPATHLEN];
+
+ lock_file_name(pp, lf, sizeof lf);
+ printf("%s:\n", pp->printer);
+
+ setres = set_qstate(SQS_DISABLEQ, lf);
+}
+
+/*
+ * Disable queuing and printing and put a message into the status file
+ * (reason for being down). If the user specified `-msg', then use
+ * everything after that as the message for the status file. If the
+ * user did NOT specify `-msg', then the command should take the first
+ * parameter as the printer name, and all remaining parameters as the
+ * message for the status file. (This is to be compatible with the
+ * original definition of 'down', which was implemented long before
+ * `-msg' was around).
+ */
+void
+down_gi(int argc, char *argv[])
+{
+
+ /* If `-msg' was specified, then this routine has nothing to do. */
+ if (generic_msg != NULL)
+ return;
+
+ /*
+ * If the user only gave one parameter, then use a default msg.
+ * (if argc == 1 at this point, then *argv == name of printer).
+ */
+ if (argc == 1) {
+ generic_msg = strdup("printing disabled\n");
+ return;
+ }
+
+ /*
+ * The user specified multiple parameters, and did not specify
+ * `-msg'. Build a message from all the parameters after the
+ * first one (and nullify those parameters so generic-processing
+ * will not process them as printer-queue names).
+ */
+ argc--;
+ argv++;
+ generic_msg = args2line(argc, argv);
+ for (; argc > 0; argc--, argv++)
+ *argv = generic_nullarg; /* "erase" it */
+}
+
+void
+down_q(struct printer *pp)
+{
+ int setres;
+ char lf[MAXPATHLEN];
+
+ lock_file_name(pp, lf, sizeof lf);
+ printf("%s:\n", pp->printer);
+
+ setres = set_qstate(SQS_DISABLEQ+SQS_STOPP, lf);
+ if (setres >= 0)
+ upstat(pp, generic_msg, 1);
+}
+
+/*
+ * Exit lpc
+ */
+void
+quit(int argc __unused, char *argv[] __unused)
+{
+ exit(0);
+}
+
+/*
+ * Kill and restart the daemon.
+ */
+void
+restart_q(struct printer *pp)
+{
+ int killres, setres, startok;
+ char lf[MAXPATHLEN];
+
+ lock_file_name(pp, lf, sizeof lf);
+ printf("%s:\n", pp->printer);
+
+ killres = kill_qtask(lf);
+
+ /*
+ * XXX - if the kill worked, we should probably sleep for
+ * a second or so before trying to restart the queue.
+ */
+
+ /* make sure the queue is set to print jobs */
+ setres = set_qstate(SQS_STARTP, lf);
+
+ PRIV_START
+ startok = startdaemon(pp);
+ PRIV_END
+ if (!startok)
+ printf("\tcouldn't restart daemon\n");
+ else
+ printf("\tdaemon restarted\n");
+}
+
+/*
+ * Set the status message of each queue listed. Requires a "-msg"
+ * parameter to indicate the end of the queue list and start of msg text.
+ */
+void
+setstatus_gi(int argc __unused, char *argv[] __unused)
+{
+
+ if (generic_msg == NULL) {
+ printf("You must specify '-msg' before the text of the new status message.\n");
+ generic_initerr = 1;
+ }
+}
+
+void
+setstatus_q(struct printer *pp)
+{
+ struct stat stbuf;
+ int not_shown;
+ char lf[MAXPATHLEN];
+
+ lock_file_name(pp, lf, sizeof lf);
+ printf("%s:\n", pp->printer);
+
+ upstat(pp, generic_msg, 1);
+
+ /*
+ * Warn the user if 'lpq' will not display this new status-message.
+ * Note that if lock file does not exist, then the queue is enabled
+ * for both queuing and printing.
+ */
+ not_shown = 1;
+ if (stat(lf, &stbuf) >= 0) {
+ if (stbuf.st_mode & LFM_PRINT_DIS)
+ not_shown = 0;
+ }
+ if (not_shown) {
+ printf("\tnote: This queue currently has printing enabled,\n");
+ printf("\t so this -msg will only be shown by 'lpq' if\n");
+ printf("\t a job is actively printing on it.\n");
+ }
+}
+
+/*
+ * Enable printing on the specified printer and startup the daemon.
+ */
+void
+start_q(struct printer *pp)
+{
+ int setres, startok;
+ char lf[MAXPATHLEN];
+
+ lock_file_name(pp, lf, sizeof lf);
+ printf("%s:\n", pp->printer);
+
+ setres = set_qstate(SQS_STARTP, lf);
+
+ PRIV_START
+ startok = startdaemon(pp);
+ PRIV_END
+ if (!startok)
+ printf("\tcouldn't start daemon\n");
+ else
+ printf("\tdaemon started\n");
+ PRIV_END
+}
+
+/*
+ * Print the status of the printer queue.
+ */
+void
+status(struct printer *pp)
+{
+ struct stat stbuf;
+ register int fd, i;
+ register struct dirent *dp;
+ DIR *dirp;
+ char file[MAXPATHLEN];
+
+ printf("%s:\n", pp->printer);
+ lock_file_name(pp, file, sizeof file);
+ if (stat(file, &stbuf) >= 0) {
+ printf("\tqueuing is %s\n",
+ ((stbuf.st_mode & LFM_QUEUE_DIS) ? "disabled"
+ : "enabled"));
+ printf("\tprinting is %s\n",
+ ((stbuf.st_mode & LFM_PRINT_DIS) ? "disabled"
+ : "enabled"));
+ } else {
+ printf("\tqueuing is enabled\n");
+ printf("\tprinting is enabled\n");
+ }
+ if ((dirp = opendir(pp->spool_dir)) == NULL) {
+ printf("\tcannot examine spool directory\n");
+ return;
+ }
+ i = 0;
+ while ((dp = readdir(dirp)) != NULL) {
+ if (*dp->d_name == 'c' && dp->d_name[1] == 'f')
+ i++;
+ }
+ closedir(dirp);
+ if (i == 0)
+ printf("\tno entries in spool area\n");
+ else if (i == 1)
+ printf("\t1 entry in spool area\n");
+ else
+ printf("\t%d entries in spool area\n", i);
+ fd = open(file, O_RDONLY);
+ if (fd < 0 || flock(fd, LOCK_SH|LOCK_NB) == 0) {
+ (void) close(fd); /* unlocks as well */
+ printf("\tprinter idle\n");
+ return;
+ }
+ (void) close(fd);
+ /* print out the contents of the status file, if it exists */
+ status_file_name(pp, file, sizeof file);
+ fd = open(file, O_RDONLY|O_SHLOCK);
+ if (fd >= 0) {
+ (void) fstat(fd, &stbuf);
+ if (stbuf.st_size > 0) {
+ putchar('\t');
+ while ((i = read(fd, line, sizeof(line))) > 0)
+ (void) fwrite(line, 1, i, stdout);
+ }
+ (void) close(fd); /* unlocks as well */
+ }
+}
+
+/*
+ * Stop the specified daemon after completing the current job and disable
+ * printing.
+ */
+void
+stop_q(struct printer *pp)
+{
+ int setres;
+ char lf[MAXPATHLEN];
+
+ lock_file_name(pp, lf, sizeof lf);
+ printf("%s:\n", pp->printer);
+
+ setres = set_qstate(SQS_STOPP, lf);
+
+ if (setres >= 0)
+ upstat(pp, "printing disabled\n", 0);
+}
+
+struct jobqueue **queue;
+int nitems;
+time_t mtime;
+
+/*
+ * Put the specified jobs at the top of printer queue.
+ */
+void
+topq(int argc, char *argv[])
+{
+ register int i;
+ struct stat stbuf;
+ int cmdstatus, changed;
+ struct printer myprinter, *pp = &myprinter;
+
+ if (argc < 3) {
+ printf("usage: topq printer [jobnum ...] [user ...]\n");
+ return;
+ }
+
+ --argc;
+ ++argv;
+ init_printer(pp);
+ cmdstatus = getprintcap(*argv, pp);
+ switch(cmdstatus) {
+ default:
+ fatal(pp, "%s", pcaperr(cmdstatus));
+ case PCAPERR_NOTFOUND:
+ printf("unknown printer %s\n", *argv);
+ return;
+ case PCAPERR_TCOPEN:
+ printf("warning: %s: unresolved tc= reference(s)", *argv);
+ break;
+ case PCAPERR_SUCCESS:
+ break;
+ }
+ printf("%s:\n", pp->printer);
+
+ PRIV_START
+ if (chdir(pp->spool_dir) < 0) {
+ printf("\tcannot chdir to %s\n", pp->spool_dir);
+ goto out;
+ }
+ PRIV_END
+ nitems = getq(pp, &queue);
+ if (nitems == 0)
+ return;
+ changed = 0;
+ mtime = queue[0]->job_time;
+ for (i = argc; --i; ) {
+ if (doarg(argv[i]) == 0) {
+ printf("\tjob %s is not in the queue\n", argv[i]);
+ continue;
+ } else
+ changed++;
+ }
+ for (i = 0; i < nitems; i++)
+ free(queue[i]);
+ free(queue);
+ if (!changed) {
+ printf("\tqueue order unchanged\n");
+ return;
+ }
+ /*
+ * Turn on the public execute bit of the lock file to
+ * get lpd to rebuild the queue after the current job.
+ */
+ PRIV_START
+ if (changed && stat(pp->lock_file, &stbuf) >= 0)
+ (void) chmod(pp->lock_file, stbuf.st_mode | LFM_RESET_QUE);
+
+out:
+ PRIV_END
+}
+
+/*
+ * Reposition the job by changing the modification time of
+ * the control file.
+ */
+static int
+touch(struct jobqueue *jq)
+{
+ struct timeval tvp[2];
+ int ret;
+
+ tvp[0].tv_sec = tvp[1].tv_sec = --mtime;
+ tvp[0].tv_usec = tvp[1].tv_usec = 0;
+ PRIV_START
+ ret = utimes(jq->job_cfname, tvp);
+ PRIV_END
+ return (ret);
+}
+
+/*
+ * Checks if specified job name is in the printer's queue.
+ * Returns: negative (-1) if argument name is not in the queue.
+ */
+static int
+doarg(char *job)
+{
+ register struct jobqueue **qq;
+ register int jobnum, n;
+ register char *cp, *machine;
+ int cnt = 0;
+ FILE *fp;
+
+ /*
+ * Look for a job item consisting of system name, colon, number
+ * (example: ucbarpa:114)
+ */
+ if ((cp = strchr(job, ':')) != NULL) {
+ machine = job;
+ *cp++ = '\0';
+ job = cp;
+ } else
+ machine = NULL;
+
+ /*
+ * Check for job specified by number (example: 112 or 235ucbarpa).
+ */
+ if (isdigit(*job)) {
+ jobnum = 0;
+ do
+ jobnum = jobnum * 10 + (*job++ - '0');
+ while (isdigit(*job));
+ for (qq = queue + nitems; --qq >= queue; ) {
+ n = 0;
+ for (cp = (*qq)->job_cfname+3; isdigit(*cp); )
+ n = n * 10 + (*cp++ - '0');
+ if (jobnum != n)
+ continue;
+ if (*job && strcmp(job, cp) != 0)
+ continue;
+ if (machine != NULL && strcmp(machine, cp) != 0)
+ continue;
+ if (touch(*qq) == 0) {
+ printf("\tmoved %s\n", (*qq)->job_cfname);
+ cnt++;
+ }
+ }
+ return(cnt);
+ }
+ /*
+ * Process item consisting of owner's name (example: henry).
+ */
+ for (qq = queue + nitems; --qq >= queue; ) {
+ PRIV_START
+ fp = fopen((*qq)->job_cfname, "r");
+ PRIV_END
+ if (fp == NULL)
+ continue;
+ while (get_line(fp) > 0)
+ if (line[0] == 'P')
+ break;
+ (void) fclose(fp);
+ if (line[0] != 'P' || strcmp(job, line+1) != 0)
+ continue;
+ if (touch(*qq) == 0) {
+ printf("\tmoved %s\n", (*qq)->job_cfname);
+ cnt++;
+ }
+ }
+ return(cnt);
+}
+
+/*
+ * Enable both queuing & printing, and start printer (undo `down').
+ */
+void
+up_q(struct printer *pp)
+{
+ int setres, startok;
+ char lf[MAXPATHLEN];
+
+ lock_file_name(pp, lf, sizeof lf);
+ printf("%s:\n", pp->printer);
+
+ setres = set_qstate(SQS_ENABLEQ+SQS_STARTP, lf);
+
+ PRIV_START
+ startok = startdaemon(pp);
+ PRIV_END
+ if (!startok)
+ printf("\tcouldn't start daemon\n");
+ else
+ printf("\tdaemon started\n");
+}
diff --git a/usr.sbin/lpr/lpc/cmdtab.c b/usr.sbin/lpr/lpc/cmdtab.c
new file mode 100644
index 000000000000..0c91c083a9b2
--- /dev/null
+++ b/usr.sbin/lpr/lpc/cmdtab.c
@@ -0,0 +1,84 @@
+/*-
+ * SPDX-License-Identifier: BSD-3-Clause
+ *
+ * Copyright (c) 1983, 1993
+ * The Regents of the University of California. All rights reserved.
+ *
+ * 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 "lp.cdefs.h" /* A cross-platform version of <sys/cdefs.h> */
+#include "lpc.h"
+#include "extern.h"
+
+/*
+ * lpc -- command tables
+ */
+char aborthelp[] = "terminate a spooling daemon immediately and disable printing";
+char botmqhelp[] = "move job(s) to the bottom of printer queue";
+char cleanhelp[] = "remove cruft files from a queue";
+char enablehelp[] = "turn a spooling queue on";
+char disablehelp[] = "turn a spooling queue off";
+char downhelp[] = "do a 'stop' followed by 'disable' and put a message in status";
+char helphelp[] = "get help on commands";
+char quithelp[] = "exit lpc";
+char restarthelp[] = "kill (if possible) and restart a spooling daemon";
+char setstatushelp[] = "set the status message of a queue, requires\n"
+ "\t\t\"-msg\" before the text of the new message";
+char starthelp[] = "enable printing and start a spooling daemon";
+char statushelp[] = "show status of daemon and queue";
+char stophelp[] = "stop a spooling daemon after current job completes and disable printing";
+char tcleanhelp[] = "test to see what files a clean cmd would remove";
+char topqhelp[] = "move job(s) to the top of printer queue";
+char uphelp[] = "enable everything and restart spooling daemon";
+
+/* Use some abbreviations so entries won't need to wrap */
+#define PR LPC_PRIVCMD
+#define M LPC_MSGOPT
+
+struct cmd cmdtab[] = {
+ { "abort", aborthelp, PR, 0, abort_q },
+ { "bottomq", botmqhelp, PR, bottomq_cmd, 0 },
+ { "clean", cleanhelp, PR, clean_gi, clean_q },
+ { "enable", enablehelp, PR, 0, enable_q },
+ { "exit", quithelp, 0, quit, 0 },
+ { "disable", disablehelp, PR, 0, disable_q },
+ { "down", downhelp, PR|M, down_gi, down_q },
+ { "help", helphelp, 0, help, 0 },
+ { "quit", quithelp, 0, quit, 0 },
+ { "restart", restarthelp, 0, 0, restart_q },
+ { "start", starthelp, PR, 0, start_q },
+ { "status", statushelp, 0, 0, status },
+ { "setstatus", setstatushelp, PR|M, setstatus_gi, setstatus_q },
+ { "stop", stophelp, PR, 0, stop_q },
+ { "tclean", tcleanhelp, 0, tclean_gi, clean_q },
+ { "topq", topqhelp, PR, topq_cmd, 0 },
+ { "up", uphelp, PR, 0, up_q },
+ { "?", helphelp, 0, help, 0 },
+ { "xtopq", topqhelp, PR, topq, 0 },
+ { 0, 0, 0, 0, 0},
+};
+
+int NCMDS = sizeof (cmdtab) / sizeof (cmdtab[0]);
diff --git a/usr.sbin/lpr/lpc/extern.h b/usr.sbin/lpr/lpc/extern.h
new file mode 100644
index 000000000000..c1cca9978d06
--- /dev/null
+++ b/usr.sbin/lpr/lpc/extern.h
@@ -0,0 +1,75 @@
+/*-
+ * SPDX-License-Identifier: BSD-3-Clause
+ *
+ * Copyright (c) 1989, 1993
+ * The Regents of the University of California. All rights reserved.
+ *
+ *
+ * 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/types.h>
+#include "lp.cdefs.h" /* A cross-platform version of <sys/cdefs.h> */
+
+/*
+ * Options for setup_myprinter().
+ */
+#define SUMP_NOHEADER 0x0001 /* Do not print a header line */
+#define SUMP_CHDIR_SD 0x0002 /* chdir into the spool directory */
+
+__BEGIN_DECLS
+void abort_q(struct printer *_pp);
+void bottomq_cmd(int _argc, char *_argv[]);
+void clean_gi(int _argc, char *_argv[]);
+void clean_q(struct printer *_pp);
+void disable_q(struct printer *_pp);
+void down_gi(int _argc, char *_argv[]);
+void down_q(struct printer *_pp);
+void enable_q(struct printer *_pp);
+void generic(void (*_specificrtn)(struct printer *_pp), int _cmdopts,
+ void (*_initcmd)(int _argc, char *_argv[]),
+ int _argc, char *_argv[]);
+void help(int _argc, char *_argv[]);
+void quit(int _argc, char *_argv[]);
+void restart_q(struct printer *_pp);
+void setstatus_gi(int _argc, char *_argv[]);
+void setstatus_q(struct printer *_pp);
+void start_q(struct printer *_pp);
+void status(struct printer *_pp);
+void stop_q(struct printer *_pp);
+void tclean_gi(int _argc, char *_argv[]);
+void topq_cmd(int _argc, char *_argv[]);
+void up_q(struct printer *_pp);
+void topq(int _argc, char *_argv[]); /* X-version */
+
+/* from lpc.c: */
+struct printer *setup_myprinter(char *_pwanted, struct printer *_pp,
+ int _sump_opts);
+__END_DECLS
+
+extern int NCMDS;
+extern struct cmd cmdtab[];
+extern uid_t uid, euid;
diff --git a/usr.sbin/lpr/lpc/lpc.8 b/usr.sbin/lpr/lpc/lpc.8
new file mode 100644
index 000000000000..089be4ab3bfc
--- /dev/null
+++ b/usr.sbin/lpr/lpc/lpc.8
@@ -0,0 +1,306 @@
+.\" Copyright (c) 1983, 1991, 1993
+.\" The Regents of the University of California. All rights reserved.
+.\"
+.\" 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.
+.\"
+.Dd July 16, 2002
+.Dt LPC 8
+.Os
+.Sh NAME
+.Nm lpc
+.Nd line printer control program
+.Sh SYNOPSIS
+.Nm
+.Op Ar command Op Ar argument ...
+.Sh DESCRIPTION
+The
+.Nm
+utility is used by the system administrator to control the
+operation of the line printer system.
+For each line printer configured in
+.Pa /etc/printcap ,
+.Nm
+may be used to:
+.Bl -bullet -offset indent
+.It
+disable or enable a printer,
+.It
+disable or enable a printer's spooling queue,
+.It
+rearrange the order of jobs in a spooling queue,
+.It
+find the status of printers, and their associated
+spooling queues and printer daemons,
+.It
+change the status message for printer queues (the status message
+may be seen by users as part of the output of the
+.Xr lpq 1
+utility).
+.El
+.Pp
+Without any arguments,
+.Nm
+will prompt for commands from the standard input.
+If arguments are supplied,
+.Nm
+interprets the first argument as a command and the remaining
+arguments as parameters to the command.
+The standard input
+may be redirected causing
+.Nm
+to read commands from file.
+Commands may be abbreviated;
+the following is the list of recognized commands.
+.Pp
+.Bl -tag -width indent -compact
+.It Ic \&? Op Ar command ...
+.It Ic help Op Ar command ...
+Print a short description of each command specified in the argument list,
+or, if no argument is given, a list of the recognized commands.
+.Pp
+.It Ic abort Brq Cm all | Ar printer
+Terminate an active spooling daemon on the local host immediately and
+then disable printing (preventing new daemons from being started by
+.Xr lpr 1 )
+for the specified printers.
+.Pp
+.It Ic bottomq Ar printer Op Ar jobspec ...
+Take the specified jobs in the order specified and move them to the
+bottom of the printer queue.
+Each
+.Ar jobspec
+can match multiple print jobs.
+The full description of a
+.Ar jobspec
+is given below.
+.Pp
+.It Ic clean Brq Cm all | Ar printer
+Remove any temporary files, data files, and control files that cannot
+be printed (i.e., do not form a complete printer job)
+from the specified printer queue(s) on the local machine.
+This command will also look for
+.Pa core
+files in spool directory
+for each printer queue, and list any that are found.
+It will not remove any
+.Pa core
+files.
+See also the
+.Ic tclean
+command.
+.Pp
+.It Ic disable Brq Cm all | Ar printer
+Turn the specified printer queues off.
+This prevents new
+printer jobs from being entered into the queue by
+.Xr lpr 1 .
+.Pp
+.It Ic down Bro Cm all | Ar printer ... Brc Cm -msg Ar message ...
+.It Ic down Bro Cm all | Ar printer Brc Ar message ...
+Turn the specified printer queue off, disable printing and put
+.Ar message
+in the printer status file.
+When specifying more than one printer queue, the
+.Ic -msg
+argument is required to separate the list of printers from the text
+that will be the new status message.
+The message does not need to be quoted, the
+remaining arguments are treated like
+.Xr echo 1 .
+This is normally used to take a printer down, and let other users
+find out why it is down (the
+.Xr lpq 1
+utility will indicate that the printer is down and will print the
+status message).
+.Pp
+.It Ic enable Brq Cm all | Ar printer
+Enable spooling on the local queue for the listed printers.
+This will allow
+.Xr lpr 1
+to put new jobs in the spool queue.
+.Pp
+.It Ic exit
+.It Ic quit
+Exit from
+.Nm .
+.Pp
+.It Ic restart Brq Cm all | Ar printer
+Attempt to start a new printer daemon.
+This is useful when some abnormal condition causes the daemon to
+die unexpectedly, leaving jobs in the queue.
+.Xr lpq 1
+will report that there is no daemon present when this condition occurs.
+If the user is the super-user,
+try to abort the current daemon first (i.e., kill and restart a stuck daemon).
+.Pp
+.It Ic setstatus Bro Cm all | Ar printer Brc Cm -msg Ar message ...
+Set the status message for the specified printers.
+The
+.Ic -msg
+argument is required to separate the list of printers from the text
+that will be the new status message.
+This is normally used to change the status message when the printer
+queue is no longer active after printing has been disabled, and you
+want to change what users will see in the output of the
+.Xr lpq 1
+utility.
+.Pp
+.It Ic start Brq Cm all | Ar printer
+Enable printing and start a spooling daemon for the listed printers.
+.Pp
+.It Ic status Brq Cm all | Ar printer
+Display the status of daemons and queues on the local machine.
+.Pp
+.It Ic stop Brq Cm all | Ar printer
+Stop a spooling daemon after the current job completes and disable
+printing.
+.Pp
+.It Ic tclean Brq Cm all | Ar printer
+This will do a test-run of the
+.Ic clean
+command.
+All the same checking is done, but the command will only print out
+messages saying what a similar
+.Ic clean
+command would do if the user typed it in.
+It will not remove any files.
+Note that the
+.Ic clean
+command is a privileged command, while the
+.Ic tclean
+command is not restricted.
+.Pp
+.It Ic topq Ar printer Op Ar jobspec ...
+Take the specified jobs in the order specified and move them to the
+top of the printer queue.
+Each
+.Ar jobspec
+can match multiple print jobs.
+The full description of a
+.Ar jobspec
+is given below.
+.Pp
+.It Ic up Brq Cm all | Ar printer
+Enable everything and start a new printer daemon.
+Undoes the effects of
+.Ic down .
+.El
+.Pp
+Commands such as
+.Ic topq
+and
+.Ic bottomq
+can take one or more
+.Ar jobspec
+to specify which jobs the command should operate on.
+A
+.Ar jobspec
+can be:
+.Bl -bullet
+.It
+a single job number, which will match all jobs in the printer's queue
+which have the same job number.
+Eg:
+.Ar 17 ,
+.It
+a range of job numbers, which will match all jobs with a number between
+the starting and ending job numbers, inclusive.
+Eg:
+.Ar 21-32 ,
+.It
+a specific userid, which will match all jobs which were sent by that
+user.
+Eg:
+.Ar jones ,
+.It
+a host name, when prefixed by an `@', which will match all jobs in
+the queue which were sent from the given host.
+Eg:
+.Ar @freebsd.org ,
+.It
+a job range and a userid, separated by a `:', which will match all jobs
+which both match the job range and were sent by the specified user.
+Eg:
+.Ar jones:17
+or
+.Ar 21-32:jones ,
+.It
+a job range and/or a userid, followed by a host name, which will match
+all jobs which match all the specified criteria.
+Eg:
+.Ar jones@freebsd.org
+or
+.Ar 21-32@freebsd.org
+or
+.Ar jones:17@freebsd.org .
+.El
+.Pp
+The values for userid and host name can also include pattern-matching
+characters, similar to the pattern matching done for filenames in
+most command shells.
+Note that if you enter a
+.Ic topq
+or
+.Ic bottomq
+command as parameters on the initial
+.Nm
+command, then the shell will expand any pattern-matching characters
+that it can (based on what files in finds in the current directory)
+before
+.Nm
+processes the command.
+In that case, any parameters which include pattern-matching characters
+should be enclosed in quotes, so that the shell will not try to
+expand them.
+.Sh FILES
+.Bl -tag -width /var/spool/*/lockx -compact
+.It Pa /etc/printcap
+printer description file
+.It Pa /var/spool/*
+spool directories
+.It Pa /var/spool/*/lock
+lock file for queue control
+.El
+.Sh DIAGNOSTICS
+.Bl -diag
+.It "?Ambiguous command"
+abbreviation matches more than one command
+.It "?Invalid command"
+no match was found
+.It "?Privileged command"
+you must be a member of group "operator" or root to execute this command
+.El
+.Sh SEE ALSO
+.Xr lpq 1 ,
+.Xr lpr 1 ,
+.Xr lprm 1 ,
+.Xr printcap 5 ,
+.Xr chkprintcap 8 ,
+.Xr lpd 8
+.Sh HISTORY
+The
+.Nm
+utility appeared in
+.Bx 4.2 .
diff --git a/usr.sbin/lpr/lpc/lpc.c b/usr.sbin/lpr/lpc/lpc.c
new file mode 100644
index 000000000000..b4db5bb2e29f
--- /dev/null
+++ b/usr.sbin/lpr/lpc/lpc.c
@@ -0,0 +1,409 @@
+/*-
+ * SPDX-License-Identifier: BSD-3-Clause
+ *
+ * Copyright (c) 1983, 1993
+ * The Regents of the University of California. All rights reserved.
+ *
+ *
+ * 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 "lp.cdefs.h" /* A cross-platform version of <sys/cdefs.h> */
+#include <sys/param.h>
+
+#include <ctype.h>
+#include <dirent.h>
+#include <err.h>
+#include <grp.h>
+#include <setjmp.h>
+#include <signal.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <syslog.h>
+#include <string.h>
+#include <unistd.h>
+#include <histedit.h>
+
+#include "lp.h"
+#include "lpc.h"
+#include "extern.h"
+
+#ifndef LPR_OPER
+#define LPR_OPER "operator" /* group name of lpr operators */
+#endif
+
+/*
+ * lpc -- line printer control program
+ */
+
+#define MAX_CMDLINE 200
+#define MAX_MARGV 20
+static int fromatty;
+
+static char cmdline[MAX_CMDLINE];
+static int margc;
+static char *margv[MAX_MARGV];
+uid_t uid, euid;
+
+int main(int _argc, char *_argv[]);
+static void cmdscanner(void);
+static struct cmd *getcmd(const char *_name);
+static void intr(int _signo);
+static void makeargv(void);
+static int ingroup(const char *_grname);
+
+int
+main(int argc, char *argv[])
+{
+ register struct cmd *c;
+
+ euid = geteuid();
+ uid = getuid();
+ PRIV_END
+ progname = argv[0];
+ openlog("lpd", 0, LOG_LPR);
+
+ if (--argc > 0) {
+ c = getcmd(*++argv);
+ if (c == (struct cmd *)-1) {
+ printf("?Ambiguous command\n");
+ exit(1);
+ }
+ if (c == NULL) {
+ printf("?Invalid command\n");
+ exit(1);
+ }
+ if ((c->c_opts & LPC_PRIVCMD) && getuid() &&
+ ingroup(LPR_OPER) == 0) {
+ printf("?Privileged command\n");
+ exit(1);
+ }
+ if (c->c_generic != NULL)
+ generic(c->c_generic, c->c_opts, c->c_handler,
+ argc, argv);
+ else
+ (*c->c_handler)(argc, argv);
+ exit(0);
+ }
+ fromatty = isatty(fileno(stdin));
+ if (!fromatty)
+ signal(SIGINT, intr);
+ for (;;) {
+ cmdscanner();
+ }
+}
+
+static void
+intr(int signo __unused)
+{
+ /* (the '__unused' is just to avoid a compile-time warning) */
+ exit(0);
+}
+
+static const char *
+lpc_prompt(void)
+{
+ return ("lpc> ");
+}
+
+/*
+ * Command parser.
+ */
+static void
+cmdscanner(void)
+{
+ register struct cmd *c;
+ static EditLine *el;
+ static History *hist;
+ HistEvent he;
+ size_t len;
+ int num;
+ const char *bp;
+
+ num = 0;
+ bp = NULL;
+ el = NULL;
+ hist = NULL;
+ for (;;) {
+ if (fromatty) {
+ if (!el) {
+ el = el_init("lpc", stdin, stdout, stderr);
+ hist = history_init();
+ history(hist, &he, H_SETSIZE, 100);
+ el_set(el, EL_HIST, history, hist);
+ el_set(el, EL_EDITOR, "emacs");
+ el_set(el, EL_PROMPT, lpc_prompt);
+ el_set(el, EL_SIGNAL, 1);
+ el_source(el, NULL);
+ /*
+ * EditLine init may call 'cgetset()' to set a
+ * capability-db meant for termcap (eg: to set
+ * terminal type 'xterm'). Reset that now, or
+ * that same db-information will be used for
+ * printcap (giving us an "xterm" printer, with
+ * all kinds of invalid capabilities...).
+ */
+ cgetset(NULL);
+ }
+ if ((bp = el_gets(el, &num)) == NULL || num == 0)
+ quit(0, NULL);
+
+ len = MIN(MAX_CMDLINE - 1, num);
+ memcpy(cmdline, bp, len);
+ cmdline[len] = 0;
+ history(hist, &he, H_ENTER, bp);
+
+ } else {
+ if (fgets(cmdline, MAX_CMDLINE, stdin) == NULL)
+ quit(0, NULL);
+ if (cmdline[0] == 0 || cmdline[0] == '\n')
+ break;
+ }
+
+ makeargv();
+ if (margc == 0)
+ continue;
+ if (el != NULL && el_parse(el, margc, (const char **)margv) != -1)
+ continue;
+
+ c = getcmd(margv[0]);
+ if (c == (struct cmd *)-1) {
+ printf("?Ambiguous command\n");
+ continue;
+ }
+ if (c == NULL) {
+ printf("?Invalid command\n");
+ continue;
+ }
+ if ((c->c_opts & LPC_PRIVCMD) && getuid() &&
+ ingroup(LPR_OPER) == 0) {
+ printf("?Privileged command\n");
+ continue;
+ }
+
+ /*
+ * Two different commands might have the same generic rtn
+ * (eg: "clean" and "tclean"), and just use different
+ * handler routines for distinct command-setup. The handler
+ * routine might also be set on a generic routine for
+ * initial parameter processing.
+ */
+ if (c->c_generic != NULL)
+ generic(c->c_generic, c->c_opts, c->c_handler,
+ margc, margv);
+ else
+ (*c->c_handler)(margc, margv);
+ }
+}
+
+static struct cmd *
+getcmd(const char *name)
+{
+ register const char *p, *q;
+ register struct cmd *c, *found;
+ register int nmatches, longest;
+
+ longest = 0;
+ nmatches = 0;
+ found = NULL;
+ for (c = cmdtab; (p = c->c_name); c++) {
+ for (q = name; *q == *p++; q++)
+ if (*q == 0) /* exact match? */
+ return(c);
+ if (!*q) { /* the name was a prefix */
+ if (q - name > longest) {
+ longest = q - name;
+ nmatches = 1;
+ found = c;
+ } else if (q - name == longest)
+ nmatches++;
+ }
+ }
+ if (nmatches > 1)
+ return((struct cmd *)-1);
+ return(found);
+}
+
+/*
+ * Slice a string up into argc/argv.
+ */
+static void
+makeargv(void)
+{
+ register char *cp;
+ register char **argp = margv;
+ register int n = 0;
+
+ margc = 0;
+ for (cp = cmdline; *cp && (size_t)(cp - cmdline) < sizeof(cmdline) &&
+ n < MAX_MARGV - 1; n++) {
+ while (isspace(*cp))
+ cp++;
+ if (*cp == '\0')
+ break;
+ *argp++ = cp;
+ margc += 1;
+ while (*cp != '\0' && !isspace(*cp))
+ cp++;
+ if (*cp == '\0')
+ break;
+ *cp++ = '\0';
+ }
+ *argp++ = NULL;
+}
+
+#define HELPINDENT (sizeof ("directory"))
+
+/*
+ * Help command.
+ */
+void
+help(int argc, char *argv[])
+{
+ register struct cmd *c;
+
+ if (argc == 1) {
+ register int i, j, w;
+ int columns, width = 0, lines;
+
+ printf("Commands may be abbreviated. Commands are:\n\n");
+ for (c = cmdtab; c->c_name; c++) {
+ int len = strlen(c->c_name);
+
+ if (len > width)
+ width = len;
+ }
+ width = (width + 8) &~ 7;
+ columns = 80 / width;
+ if (columns == 0)
+ columns = 1;
+ lines = (NCMDS + columns - 1) / columns;
+ for (i = 0; i < lines; i++) {
+ for (j = 0; j < columns; j++) {
+ c = cmdtab + j * lines + i;
+ if (c->c_name)
+ printf("%s", c->c_name);
+ if (c + lines >= &cmdtab[NCMDS]) {
+ printf("\n");
+ break;
+ }
+ w = strlen(c->c_name);
+ while (w < width) {
+ w = (w + 8) &~ 7;
+ putchar('\t');
+ }
+ }
+ }
+ return;
+ }
+ while (--argc > 0) {
+ register char *arg;
+ arg = *++argv;
+ c = getcmd(arg);
+ if (c == (struct cmd *)-1)
+ printf("?Ambiguous help command %s\n", arg);
+ else if (c == (struct cmd *)0)
+ printf("?Invalid help command %s\n", arg);
+ else
+ printf("%-*s\t%s\n", (int) HELPINDENT,
+ c->c_name, c->c_help);
+ }
+}
+
+/*
+ * return non-zero if the user is a member of the given group
+ */
+static int
+ingroup(const char *grname)
+{
+ static struct group *gptr=NULL;
+ static int ngroups = 0;
+ static long ngroups_max;
+ static gid_t *groups;
+ register gid_t gid;
+ register int i;
+
+ if (gptr == NULL) {
+ if ((gptr = getgrnam(grname)) == NULL) {
+ warnx("warning: unknown group '%s'", grname);
+ return(0);
+ }
+ ngroups_max = sysconf(_SC_NGROUPS_MAX);
+ if ((groups = malloc(sizeof(gid_t) * ngroups_max)) == NULL)
+ err(1, "malloc");
+ ngroups = getgroups(ngroups_max, groups);
+ if (ngroups < 0)
+ err(1, "getgroups");
+ }
+ gid = gptr->gr_gid;
+ if (gid == getegid())
+ return(1);
+ for (i = 0; i < ngroups; i++)
+ if (gid == groups[i])
+ return(1);
+ return(0);
+}
+
+/*
+ * Routine to get the information for a single printer (which will be
+ * called by the routines which implement individual commands).
+ * Note: This is for commands operating on a *single* printer.
+ */
+struct printer *
+setup_myprinter(char *pwanted, struct printer *pp, int sump_opts)
+{
+ int cdres, cmdstatus;
+
+ init_printer(pp);
+ cmdstatus = getprintcap(pwanted, pp);
+ switch (cmdstatus) {
+ default:
+ fatal(pp, "%s", pcaperr(cmdstatus));
+ /* NOTREACHED */
+ case PCAPERR_NOTFOUND:
+ printf("unknown printer %s\n", pwanted);
+ return (NULL);
+ case PCAPERR_TCOPEN:
+ printf("warning: %s: unresolved tc= reference(s)", pwanted);
+ break;
+ case PCAPERR_SUCCESS:
+ break;
+ }
+ if ((sump_opts & SUMP_NOHEADER) == 0)
+ printf("%s:\n", pp->printer);
+
+ if (sump_opts & SUMP_CHDIR_SD) {
+ PRIV_START
+ cdres = chdir(pp->spool_dir);
+ PRIV_END
+ if (cdres < 0) {
+ printf("\tcannot chdir to %s\n", pp->spool_dir);
+ free_printer(pp);
+ return (NULL);
+ }
+ }
+
+ return (pp);
+}
diff --git a/usr.sbin/lpr/lpc/lpc.h b/usr.sbin/lpr/lpc/lpc.h
new file mode 100644
index 000000000000..958956fa33ff
--- /dev/null
+++ b/usr.sbin/lpr/lpc/lpc.h
@@ -0,0 +1,49 @@
+/*-
+ * SPDX-License-Identifier: BSD-3-Clause
+ *
+ * Copyright (c) 1983, 1993
+ * The Regents of the University of California. All rights reserved.
+ *
+ * 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.
+ */
+
+/*
+ * Line Printer Control (lpc) program.
+ */
+struct printer;
+
+#define LPC_PRIVCMD 0x0001 /* a privileged command */
+#define LPC_MSGOPT 0x0002 /* command recognizes -msg option */
+
+struct cmd {
+ const char *c_name; /* command name */
+ const char *c_help; /* help message */
+ const int c_opts; /* flags (eg: privileged command) */
+ /* routine to do all the work for plain cmds, or
+ * initialization work for generic-printer cmds: */
+ void (*c_handler)(int, char *[]);
+ /* routine to do the work for generic-printer cmds: */
+ void (*c_generic)(struct printer *);
+};
diff --git a/usr.sbin/lpr/lpc/movejobs.c b/usr.sbin/lpr/lpc/movejobs.c
new file mode 100644
index 000000000000..35fdcaec3b0c
--- /dev/null
+++ b/usr.sbin/lpr/lpc/movejobs.c
@@ -0,0 +1,271 @@
+/*-
+ * SPDX-License-Identifier: BSD-2-Clause
+ *
+ * ------+---------+---------+---------+---------+---------+---------+---------*
+ * Copyright (c) 2002 - Garance Alistair Drosehn <gad@FreeBSD.org>.
+ * All rights reserved.
+ *
+ * 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.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR 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 AUTHOR 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.
+ *
+ * The views and conclusions contained in the software and documentation
+ * are those of the authors and should not be interpreted as representing
+ * official policies, either expressed or implied, of the FreeBSD Project
+ * or FreeBSD, Inc.
+ *
+ * ------+---------+---------+---------+---------+---------+---------+---------*
+ */
+
+#include "lp.cdefs.h" /* A cross-platform version of <sys/cdefs.h> */
+/*
+ * movejobs.c - The lpc commands which move jobs around.
+ */
+
+#include <sys/file.h>
+#include <sys/param.h>
+#include <sys/queue.h>
+#include <sys/time.h>
+#include <sys/stat.h>
+
+#include <ctype.h>
+#include <dirent.h> /* just for MAXNAMLEN, for job_cfname in lp.h! */
+#include <err.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+#include "lp.h"
+#include "lpc.h"
+#include "matchjobs.h"
+#include "extern.h"
+
+/* Values for origcmd in tqbq_common() */
+#define IS_TOPQ 1
+#define IS_BOTQ 2
+
+static int process_jobs(int _argc, char *_argv[], process_jqe
+ _process_rtn, void *myinfo);
+static process_jqe touch_jqe;
+static void tqbq_common(int _argc, char *_argv[], int _origcmd);
+
+/*
+ * isdigit is defined to work on an 'int', in the range 0 to 255, plus EOF.
+ * Define a wrapper which can take 'char', either signed or unsigned.
+ */
+#define isdigitch(Anychar) isdigit(((int) Anychar) & 255)
+
+struct touchjqe_info { /* for topq/bottomq */
+ time_t newtime;
+};
+
+static int nitems;
+static struct jobqueue **queue;
+
+/*
+ * Process all the jobs, as specified by the user.
+ */
+static int
+process_jobs(int argc, char *argv[], process_jqe process_rtn, void *myinfo)
+{
+ struct jobspec_hdr jobs_wanted;
+ int i, matchcnt, pjres;
+
+ STAILQ_INIT(&jobs_wanted);
+ for (i = 0; i < argc; i++) {
+ pjres = parse_jobspec(argv[i], &jobs_wanted);
+ if (pjres == 0) {
+ printf("\tinvalid job specifier: %s\n", argv[i]);
+ continue;
+ }
+ }
+ matchcnt = scanq_jobspec(nitems, queue, SCQ_JSORDER, &jobs_wanted,
+ process_rtn, myinfo);
+
+ free_jobspec(&jobs_wanted);
+ return (matchcnt);
+}
+
+/*
+ * Reposition the job by changing the modification time of the
+ * control file.
+ */
+static int
+touch_jqe(void *myinfo, struct jobqueue *jq, struct jobspec *jspec)
+{
+ struct timeval tvp[2];
+ struct touchjqe_info *touch_info;
+ int ret;
+
+ /*
+ * If the entire queue has been scanned for the current jobspec,
+ * then let the user know if there were no jobs matched by that
+ * specification.
+ */
+ if (jq == NULL) {
+ if (jspec->matchcnt == 0) {
+ format_jobspec(jspec, FMTJS_VERBOSE);
+ if (jspec->pluralfmt)
+ printf("\tjobs %s are not in the queue\n",
+ jspec->fmtoutput);
+ else
+ printf("\tjob %s is not in the queue\n",
+ jspec->fmtoutput);
+ }
+ return (1);
+ }
+
+ /*
+ * Do a little juggling with "matched" vs "processed", so a single
+ * job can be matched by multiple specifications, and yet it will
+ * be moved only once. This is so, eg, 'topq lp 7 7' will not
+ * complain "job 7 is not in queue" for the second specification.
+ */
+ jq->job_matched = 0;
+ if (jq->job_processed) {
+ printf("\tmoved %s earlier\n", jq->job_cfname);
+ return (1);
+ }
+ jq->job_processed = 1;
+
+ touch_info = myinfo;
+ tvp[0].tv_sec = tvp[1].tv_sec = ++touch_info->newtime;
+ tvp[0].tv_usec = tvp[1].tv_usec = 0;
+ PRIV_START
+ ret = utimes(jq->job_cfname, tvp);
+ PRIV_END
+
+ if (ret == 0) {
+ if (jspec->matcheduser)
+ printf("\tmoved %s (user %s)\n", jq->job_cfname,
+ jspec->matcheduser);
+ else
+ printf("\tmoved %s\n", jq->job_cfname);
+ }
+ return (ret);
+}
+
+/*
+ * Put the specified jobs at the bottom of printer queue.
+ */
+void
+bottomq_cmd(int argc, char *argv[])
+{
+
+ if (argc < 3) {
+ printf("usage: bottomq printer [jobspec ...]\n");
+ return;
+ }
+ --argc; /* First argv was the command name */
+ ++argv;
+
+ tqbq_common(argc, argv, IS_BOTQ);
+}
+
+/*
+ * Put the specified jobs at the top of printer queue.
+ */
+void
+topq_cmd(int argc, char *argv[])
+{
+
+ if (argc < 3) {
+ printf("usage: topq printer [jobspec ...]\n");
+ return;
+ }
+ --argc; /* First argv was the command name */
+ ++argv;
+
+ tqbq_common(argc, argv, IS_TOPQ);
+}
+
+/*
+ * Processing in common between topq and bottomq commands.
+ */
+void
+tqbq_common(int argc, char *argv[], int origcmd)
+{
+ struct printer myprinter, *pp;
+ struct touchjqe_info touch_info;
+ int i, movecnt, setres;
+
+ pp = setup_myprinter(*argv, &myprinter, SUMP_CHDIR_SD);
+ if (pp == NULL)
+ return;
+ --argc; /* Second argv was the printer name */
+ ++argv;
+
+ nitems = getq(pp, &queue);
+ if (nitems == 0) {
+ printf("\tthere are no jobs in the queue\n");
+ free_printer(pp);
+ return;
+ }
+
+ /*
+ * The only real difference between topq and bottomq is the
+ * initial value used for newtime.
+ */
+ switch (origcmd) {
+ case IS_BOTQ:
+ /*
+ * When moving jobs to the bottom of the queue, pick a
+ * starting value which is one second after the last job
+ * in the queue.
+ */
+ touch_info.newtime = queue[nitems - 1]->job_time + 1;
+ break;
+ case IS_TOPQ:
+ /*
+ * When moving jobs to the top of the queue, the greatest
+ * number of jobs which could be moved is all the jobs
+ * that are in the queue. Pick a starting value which
+ * leaves plenty of room for all existing jobs.
+ */
+ touch_info.newtime = queue[0]->job_time - nitems - 5;
+ break;
+ default:
+ printf("\ninternal error in topq/bottomq processing.\n");
+ return;
+ }
+
+ movecnt = process_jobs(argc, argv, touch_jqe, &touch_info);
+
+ /*
+ * If any jobs were moved, then chmod the lock file to notify any
+ * active process for this queue that the queue has changed, so
+ * it will rescan the queue to find out the new job order.
+ */
+ if (movecnt == 0)
+ printf("\tqueue order unchanged\n");
+ else {
+ setres = set_qstate(SQS_QCHANGED, pp->lock_file);
+ if (setres < 0)
+ printf("\t* queue order changed for %s, but the\n"
+ "\t* attempt to set_qstate() failed [%d]!\n",
+ pp->printer, setres);
+ }
+
+ for (i = 0; i < nitems; i++)
+ free(queue[i]);
+ free(queue);
+ free_printer(pp);
+}
+
diff --git a/usr.sbin/lpr/lpd/Makefile b/usr.sbin/lpr/lpd/Makefile
new file mode 100644
index 000000000000..199f03ed16a0
--- /dev/null
+++ b/usr.sbin/lpr/lpd/Makefile
@@ -0,0 +1,13 @@
+PACKAGE=lp
+CONFS= hosts.lpd printcap
+PROG= lpd
+MAN= lpd.8
+SRCS= lpd.c printjob.c recvjob.c lpdchar.c modes.c
+
+CFLAGS+= -I${.CURDIR:H}/common_source
+
+WARNS?= 1
+
+LIBADD= lpr
+
+.include <bsd.prog.mk>
diff --git a/usr.sbin/lpr/lpd/Makefile.depend b/usr.sbin/lpr/lpd/Makefile.depend
new file mode 100644
index 000000000000..f72fd2df5270
--- /dev/null
+++ b/usr.sbin/lpr/lpd/Makefile.depend
@@ -0,0 +1,17 @@
+# Autogenerated - do NOT edit!
+
+DIRDEPS = \
+ include \
+ include/arpa \
+ include/xlocale \
+ lib/${CSU_DIR} \
+ lib/libc \
+ lib/libcompiler_rt \
+ usr.sbin/lpr/common_source \
+
+
+.include <dirdeps.mk>
+
+.if ${DEP_RELDIR} == ${_DEP_RELDIR}
+# local dependencies - needed for -jN in clean tree
+.endif
diff --git a/usr.sbin/lpr/lpd/extern.h b/usr.sbin/lpr/lpd/extern.h
new file mode 100644
index 000000000000..71b02ea904f4
--- /dev/null
+++ b/usr.sbin/lpr/lpd/extern.h
@@ -0,0 +1,45 @@
+/*-
+ * SPDX-License-Identifier: BSD-3-Clause
+ *
+ * Copyright (c) 1989, 1993
+ * The Regents of the University of California. All rights reserved.
+ *
+ * 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 "lp.cdefs.h" /* A cross-platform version of <sys/cdefs.h> */
+
+extern char scnkey[][HEIGHT]; /* in lpdchar.c */
+extern int lflag; /* in lpd.c */
+
+struct printer;
+struct termios;
+
+__BEGIN_DECLS
+void printjob(struct printer *_pp);
+void startprinting(const char *_printer);
+void recvjob(const char *_printer);
+int msearch(char *_str, struct termios *_ip);
+__END_DECLS
diff --git a/usr.sbin/lpr/lpd/hosts.lpd b/usr.sbin/lpr/lpd/hosts.lpd
new file mode 100644
index 000000000000..c440bdec992e
--- /dev/null
+++ b/usr.sbin/lpr/lpd/hosts.lpd
@@ -0,0 +1,3 @@
+#
+# See lpd(8)
+#machine.domain
diff --git a/usr.sbin/lpr/lpd/lpd.8 b/usr.sbin/lpr/lpd/lpd.8
new file mode 100644
index 000000000000..eed37ea1d6f4
--- /dev/null
+++ b/usr.sbin/lpr/lpd/lpd.8
@@ -0,0 +1,351 @@
+.\" Copyright (c) 1983, 1991, 1993
+.\" The Regents of the University of California. All rights reserved.
+.\"
+.\" 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.
+.\"
+.Dd April 15, 2021
+.Dt LPD 8
+.Os
+.Sh NAME
+.Nm lpd
+.Nd line printer spooler daemon
+.Sh SYNOPSIS
+.Nm
+.Op Fl cdlpsFW46
+.Op Ar port#
+.Sh DESCRIPTION
+The
+.Nm
+utility
+is the line printer daemon (spool area handler) and is normally invoked
+at boot time from the
+.Xr rc 8
+file.
+It makes a single pass through the
+.Xr printcap 5
+file to find out about the existing printers and
+prints any files left after a crash.
+It then uses the system calls
+.Xr listen 2
+and
+.Xr accept 2
+to receive requests to print files in the queue,
+transfer files to the spooling area, display the queue,
+or remove jobs from the queue.
+In each case, it forks a child to handle
+the request so the parent can continue to listen for more requests.
+.Pp
+Available options:
+.Bl -tag -width Ds
+.It Fl c
+By default, if some remote host has a connection error while trying to
+send a print request to
+.Nm
+on a local host,
+.Nm
+will only send error message to that remote host.
+The
+.Fl c
+flag causes
+.Nm
+to also log all of those connection errors via
+.Xr syslog 3 .
+.It Fl d
+Turn on
+.Dv SO_DEBUG
+on the Internet listening socket (see
+.Xr setsockopt 2 ) .
+.It Fl l
+The
+.Fl l
+flag causes
+.Nm
+to log valid requests received from the network.
+This can be useful
+for debugging purposes.
+.It Fl p
+The
+.Fl p
+flag is a synonym for the
+.Fl s
+flag.
+It is being deprecated, and may be removed in a
+future version of
+.Nm .
+.It Fl s
+The
+.Fl s
+(secure) flag causes
+.Nm
+not to open an Internet listening socket.
+This means that
+.Nm
+will not accept any connections from any remote
+hosts, although it will still accept print requests
+from all local users.
+.It Fl F
+By default,
+.Nm
+will daemonize into the background.
+The
+.Fl F
+flag causes
+.Nm
+to remain in the foreground.
+Logging is still performed with
+.Xr syslog 3 .
+.It Fl W
+By default, the
+.Nm
+daemon will only accept connections which originate
+from a reserved-port (<1024) on the remote host.
+The
+.Fl W
+flag causes
+.Nm
+to accept connections coming from any port.
+This is can be useful when you want to accept print jobs
+from certain implementations of lpr written for Windows.
+.It Fl 4
+Inet only.
+.It Fl 6
+Inet6 only.
+.It Fl 46
+Inet and inet6 (default).
+.It Ar "port#"
+The Internet port number used to rendezvous
+with other processes is normally obtained with
+.Xr getservbyname 3
+but can be changed with the
+.Ar port#
+argument.
+.El
+.Pp
+Access control is provided by two means.
+First, all requests must come from
+one of the machines listed in the file
+.Pa /etc/hosts.equiv
+or
+.Pa /etc/hosts.lpd .
+Second, if the
+.Li rs
+capability is specified in the
+.Xr printcap 5
+entry for the printer being accessed,
+.Em lpr
+requests will only be honored for those users with accounts on the
+machine with the printer.
+.Pp
+The file
+.Em minfree
+in each spool directory contains the number of kilobytes to leave free
+so that the line printer queue will not completely fill the disk.
+The
+.Em minfree
+file can be edited with your favorite text editor.
+.Pp
+The daemon begins processing files
+after it has successfully set the lock for exclusive
+access (described a bit later),
+and scans the spool directory
+for files beginning with
+.Em cf .
+Lines in each
+.Em cf
+file specify files to be printed or non-printing actions to be
+performed.
+Each such line begins with a key character
+to specify what to do with the remainder of the line.
+.Bl -tag -width Ds
+.It J
+Job Name.
+String to be used for the job name on the burst page.
+.It C
+Classification.
+String to be used for the classification line
+on the burst page.
+.It L
+Literal.
+The line contains identification info from
+the password file and causes the banner page to be printed.
+.It T
+Title.
+String to be used as the title for
+.Xr pr 1 .
+.It H
+Host Name.
+Name of the machine where
+.Xr lpr 1
+was invoked.
+.It P
+Person.
+Login name of the person who invoked
+.Xr lpr 1 .
+This is used to verify ownership by
+.Xr lprm 1 .
+.It M
+Send mail to the specified user when the current print job completes.
+.It f
+Formatted File.
+Name of a file to print which is already formatted.
+.It l
+Like ``f'' but passes control characters and does not make page breaks.
+.It p
+Name of a file to print using
+.Xr pr 1
+as a filter.
+.It t
+Troff File.
+The file contains
+.Xr troff 1 Pq Pa ports/textproc/groff
+output (cat phototypesetter commands).
+.It n
+Ditroff File.
+The file contains device independent troff
+output.
+.It r
+DVI File.
+The file contains
+.Tn Tex l
+output
+DVI format from Stanford.
+.It g
+Graph File.
+The file contains data produced by
+.Xr plot 3 .
+.It c
+Cifplot File.
+The file contains data produced by
+.Em cifplot .
+.It v
+The file contains a raster image.
+.It r
+The file contains text data with
+FORTRAN carriage control characters.
+.It \&1
+Troff Font R.
+Name of the font file to use instead of the default.
+.It \&2
+Troff Font I.
+Name of the font file to use instead of the default.
+.It \&3
+Troff Font B.
+Name of the font file to use instead of the default.
+.It \&4
+Troff Font S.
+Name of the font file to use instead of the default.
+.It W
+Width.
+Changes the page width (in characters) used by
+.Xr pr 1
+and the text filters.
+.It I
+Indent.
+The number of characters to indent the output by (in ASCII).
+.It U
+Unlink.
+Name of file to remove upon completion of printing.
+.It N
+File name.
+The name of the file which is being printed, or a blank
+for the standard input (when
+.Xr lpr 1
+is invoked in a pipeline).
+.It Z
+Locale.
+String to be used as the locale for
+.Xr pr 1 .
+.El
+.Pp
+If a file cannot be opened, a message will be logged via
+.Xr syslog 3
+using the
+.Em LOG_LPR
+facility.
+The
+.Nm
+utility will try up to 20 times
+to reopen a file it expects to be there, after which it will
+skip the file to be printed.
+.Pp
+The
+.Nm
+utility uses
+.Xr flock 2
+to provide exclusive access to the lock file and to prevent multiple
+daemons from becoming active simultaneously.
+If the daemon should be killed
+or die unexpectedly, the lock file need not be removed.
+The lock file is kept in a readable
+.Tn ASCII
+form
+and contains two lines.
+The first is the process id of the daemon and the second is the control
+file name of the current job being printed.
+The second line is updated to
+reflect the current status of
+.Nm
+for the programs
+.Xr lpq 1
+and
+.Xr lprm 1 .
+.Sh FILES
+.Bl -tag -width "/var/spool/*/minfree" -compact
+.It Pa /etc/printcap
+printer description file
+.It Pa /var/spool/*
+spool directories
+.It Pa /var/spool/*/minfree
+minimum free space to leave
+.It Pa /dev/lp*
+line printer devices
+.It Pa /var/run/printer
+socket for local requests
+.It Pa /etc/hosts.equiv
+lists machine names allowed printer access
+.It Pa /etc/hosts.lpd
+lists machine names allowed printer access,
+but not under same administrative control.
+.El
+.Sh SEE ALSO
+.Xr lpq 1 ,
+.Xr lpr 1 ,
+.Xr lprm 1 ,
+.Xr setsockopt 2 ,
+.Xr syslog 3 ,
+.Xr hosts.lpd 5 ,
+.Xr printcap 5 ,
+.Xr chkprintcap 8 ,
+.Xr lpc 8 ,
+.Xr pac 8
+.Rs
+.\" 4.4BSD SMM:7
+.%A Ralph Campbell
+.%T "4.2 BSD Line Printer Spooler Manual"
+.Re
+.Sh HISTORY
+An
+.Nm
+daemon appeared in Version 6 AT&T UNIX.
diff --git a/usr.sbin/lpr/lpd/lpd.c b/usr.sbin/lpr/lpd/lpd.c
new file mode 100644
index 000000000000..089b8fedc2d5
--- /dev/null
+++ b/usr.sbin/lpr/lpd/lpd.c
@@ -0,0 +1,934 @@
+/*-
+ * SPDX-License-Identifier: BSD-3-Clause
+ *
+ * Copyright (c) 1983, 1993, 1994
+ * The Regents of the University of California. All rights reserved.
+ *
+ *
+ * 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 "lp.cdefs.h" /* A cross-platform version of <sys/cdefs.h> */
+/*
+ * lpd -- line printer daemon.
+ *
+ * Listen for a connection and perform the requested operation.
+ * Operations are:
+ * \1printer\n
+ * check the queue for jobs and print any found.
+ * \2printer\n
+ * receive a job from another machine and queue it.
+ * \3printer [users ...] [jobs ...]\n
+ * return the current state of the queue (short form).
+ * \4printer [users ...] [jobs ...]\n
+ * return the current state of the queue (long form).
+ * \5printer person [users ...] [jobs ...]\n
+ * remove jobs from the queue.
+ *
+ * Strategy to maintain protected spooling area:
+ * 1. Spooling area is writable only by daemon and spooling group
+ * 2. lpr runs setuid root and setgrp spooling group; it uses
+ * root to access any file it wants (verifying things before
+ * with an access call) and group id to know how it should
+ * set up ownership of files in the spooling area.
+ * 3. Files in spooling area are owned by root, group spooling
+ * group, with mode 660.
+ * 4. lpd, lpq and lprm run setuid daemon and setgrp spooling group to
+ * access files and printer. Users can't get to anything
+ * w/o help of lpq and lprm programs.
+ */
+
+#include <sys/param.h>
+#include <sys/wait.h>
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <sys/un.h>
+#include <sys/stat.h>
+#include <sys/file.h>
+#include <netinet/in.h>
+#include <arpa/inet.h>
+
+#include <netdb.h>
+#include <unistd.h>
+#include <syslog.h>
+#include <signal.h>
+#include <err.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <dirent.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sysexits.h>
+#include <ctype.h>
+#include "lp.h"
+#include "lp.local.h"
+#include "pathnames.h"
+#include "extern.h"
+
+int lflag; /* log requests flag */
+int sflag; /* no incoming port flag */
+int Fflag; /* run in foreground flag */
+int from_remote; /* from remote socket */
+
+int main(int argc, char **_argv);
+static void reapchild(int _signo);
+static void mcleanup(int _signo);
+static void doit(void);
+static void startup(void);
+static void chkhost(struct sockaddr *_f, int _ch_opts);
+static int ckqueue(struct printer *_pp);
+static void fhosterr(int _ch_opts, char *_sysmsg, char *_usermsg);
+static int *socksetup(int _af, int _debuglvl);
+static void usage(void);
+
+/* XXX from libc/net/rcmd.c */
+extern int __ivaliduser_sa(FILE *, struct sockaddr *, socklen_t,
+ const char *, const char *);
+
+uid_t uid, euid;
+
+#define LPD_NOPORTCHK 0001 /* skip reserved-port check */
+#define LPD_LOGCONNERR 0002 /* (sys)log connection errors */
+#define LPD_ADDFROMLINE 0004 /* just used for fhosterr() */
+
+int
+main(int argc, char **argv)
+{
+ int ch_options, errs, f, funix, *finet, i, lfd, socket_debug;
+ fd_set defreadfds;
+ struct sockaddr_un un, fromunix;
+ struct sockaddr_storage frominet;
+ socklen_t fromlen;
+ sigset_t omask, nmask;
+ struct servent *sp, serv;
+ int inet_flag = 0, inet6_flag = 0;
+
+ euid = geteuid(); /* these shouldn't be different */
+ uid = getuid();
+
+ ch_options = 0;
+ socket_debug = 0;
+ gethostname(local_host, sizeof(local_host));
+
+ progname = "lpd";
+
+ if (euid != 0)
+ errx(EX_NOPERM,"must run as root");
+
+ errs = 0;
+ while ((i = getopt(argc, argv, "cdlpswFW46")) != -1)
+ switch (i) {
+ case 'c':
+ /* log all kinds of connection-errors to syslog */
+ ch_options |= LPD_LOGCONNERR;
+ break;
+ case 'd':
+ socket_debug++;
+ break;
+ case 'l':
+ lflag++;
+ break;
+ case 'p': /* letter initially used for -s */
+ /*
+ * This will probably be removed with 5.0-release.
+ */
+ /* FALLTHROUGH */
+ case 's': /* secure (no inet) */
+ sflag++;
+ break;
+ case 'w': /* netbsd uses -w for maxwait */
+ /*
+ * This will be removed after the release of 4.4, as
+ * it conflicts with -w in netbsd's lpd. For now it
+ * is just a warning, so we won't suddenly break lpd
+ * for anyone who is currently using the option.
+ */
+ syslog(LOG_WARNING,
+ "NOTE: the -w option has been renamed -W");
+ syslog(LOG_WARNING,
+ "NOTE: please change your lpd config to use -W");
+ /* FALLTHROUGH */
+ case 'F':
+ Fflag++;
+ break;
+ case 'W':
+ /* allow connections coming from a non-reserved port */
+ /* (done by some lpr-implementations for MS-Windows) */
+ ch_options |= LPD_NOPORTCHK;
+ break;
+ case '4':
+ family = PF_INET;
+ inet_flag++;
+ break;
+ case '6':
+#ifdef INET6
+ family = PF_INET6;
+ inet6_flag++;
+#else
+ errx(EX_USAGE, "lpd compiled sans INET6 (IPv6 support)");
+#endif
+ break;
+ /*
+ * The following options are not in FreeBSD (yet?), but are
+ * listed here to "reserve" them, because the option-letters
+ * are used by either NetBSD or OpenBSD (as of July 2001).
+ */
+ case 'b': /* set bind-addr */
+ case 'n': /* set max num of children */
+ case 'r': /* allow 'of' for remote ptrs */
+ /* ...[not needed in freebsd] */
+ /* FALLTHROUGH */
+ default:
+ errs++;
+ }
+ if (inet_flag && inet6_flag)
+ family = PF_UNSPEC;
+ argc -= optind;
+ argv += optind;
+ if (errs)
+ usage();
+
+ if (argc == 1) {
+ if ((i = atoi(argv[0])) == 0)
+ usage();
+ if (i < 0 || i > USHRT_MAX)
+ errx(EX_USAGE, "port # %d is invalid", i);
+
+ serv.s_port = htons(i);
+ sp = &serv;
+ argc--;
+ } else {
+ sp = getservbyname("printer", "tcp");
+ if (sp == NULL)
+ errx(EX_OSFILE, "printer/tcp: unknown service");
+ }
+
+ if (argc != 0)
+ usage();
+
+ /*
+ * We run chkprintcap right away to catch any errors and blat them
+ * to stderr while we still have it open, rather than sending them
+ * to syslog and leaving the user wondering why lpd started and
+ * then stopped. There should probably be a command-line flag to
+ * ignore errors from chkprintcap.
+ */
+ {
+ pid_t pid;
+ int status;
+ pid = fork();
+ if (pid < 0) {
+ err(EX_OSERR, "cannot fork");
+ } else if (pid == 0) { /* child */
+ execl(_PATH_CHKPRINTCAP, _PATH_CHKPRINTCAP, (char *)0);
+ err(EX_OSERR, "cannot execute %s", _PATH_CHKPRINTCAP);
+ }
+ if (waitpid(pid, &status, 0) < 0) {
+ err(EX_OSERR, "cannot wait");
+ }
+ if (WIFEXITED(status) && WEXITSTATUS(status) != 0)
+ errx(EX_OSFILE, "%d errors in printcap file, exiting",
+ WEXITSTATUS(status));
+ }
+
+#ifdef DEBUG
+ Fflag++;
+#endif
+ /*
+ * Set up standard environment by detaching from the parent
+ * if -F not specified
+ */
+ if (Fflag == 0) {
+ daemon(0, 0);
+ }
+
+ openlog("lpd", LOG_PID, LOG_LPR);
+ syslog(LOG_INFO, "lpd startup: logging=%d%s%s", lflag,
+ socket_debug ? " dbg" : "", sflag ? " net-secure" : "");
+ (void) umask(0);
+ /*
+ * NB: This depends on O_NONBLOCK semantics doing the right thing;
+ * i.e., applying only to the O_EXLOCK and not to the rest of the
+ * open/creation. As of 1997-12-02, this is the case for commonly-
+ * used filesystems. There are other places in this code which
+ * make the same assumption.
+ */
+ lfd = open(_PATH_MASTERLOCK, O_WRONLY|O_CREAT|O_EXLOCK|O_NONBLOCK,
+ LOCK_FILE_MODE);
+ if (lfd < 0) {
+ if (errno == EWOULDBLOCK) /* active daemon present */
+ exit(0);
+ syslog(LOG_ERR, "%s: %m", _PATH_MASTERLOCK);
+ exit(1);
+ }
+ fcntl(lfd, F_SETFL, 0); /* turn off non-blocking mode */
+ ftruncate(lfd, 0);
+ /*
+ * write process id for others to know
+ */
+ sprintf(line, "%u\n", getpid());
+ f = strlen(line);
+ if (write(lfd, line, f) != f) {
+ syslog(LOG_ERR, "%s: %m", _PATH_MASTERLOCK);
+ exit(1);
+ }
+ signal(SIGCHLD, reapchild);
+ /*
+ * Restart all the printers.
+ */
+ startup();
+ (void) unlink(_PATH_SOCKETNAME);
+ funix = socket(AF_UNIX, SOCK_STREAM, 0);
+ if (funix < 0) {
+ syslog(LOG_ERR, "socket: %m");
+ exit(1);
+ }
+
+ sigemptyset(&nmask);
+ sigaddset(&nmask, SIGHUP);
+ sigaddset(&nmask, SIGINT);
+ sigaddset(&nmask, SIGQUIT);
+ sigaddset(&nmask, SIGTERM);
+ sigprocmask(SIG_BLOCK, &nmask, &omask);
+
+ (void) umask(077);
+ signal(SIGHUP, mcleanup);
+ signal(SIGINT, mcleanup);
+ signal(SIGQUIT, mcleanup);
+ signal(SIGTERM, mcleanup);
+ memset(&un, 0, sizeof(un));
+ un.sun_family = AF_UNIX;
+ strcpy(un.sun_path, _PATH_SOCKETNAME);
+#ifndef SUN_LEN
+#define SUN_LEN(unp) (strlen((unp)->sun_path) + 2)
+#endif
+ if (bind(funix, (struct sockaddr *)&un, SUN_LEN(&un)) < 0) {
+ syslog(LOG_ERR, "ubind: %m");
+ exit(1);
+ }
+ (void) umask(0);
+ sigprocmask(SIG_SETMASK, &omask, (sigset_t *)0);
+ FD_ZERO(&defreadfds);
+ FD_SET(funix, &defreadfds);
+ listen(funix, 5);
+ if (sflag == 0) {
+ finet = socksetup(family, socket_debug);
+ } else
+ finet = NULL; /* pretend we couldn't open TCP socket. */
+ if (finet) {
+ for (i = 1; i <= *finet; i++) {
+ FD_SET(finet[i], &defreadfds);
+ listen(finet[i], 5);
+ }
+ }
+ /*
+ * Main loop: accept, do a request, continue.
+ */
+ memset(&frominet, 0, sizeof(frominet));
+ memset(&fromunix, 0, sizeof(fromunix));
+ if (lflag)
+ syslog(LOG_INFO, "lpd startup: ready to accept requests");
+ /*
+ * XXX - should be redone for multi-protocol
+ */
+ for (;;) {
+ int domain, nfds, s;
+ fd_set readfds;
+
+ FD_COPY(&defreadfds, &readfds);
+ nfds = select(20, &readfds, 0, 0, 0);
+ if (nfds <= 0) {
+ if (nfds < 0 && errno != EINTR)
+ syslog(LOG_WARNING, "select: %m");
+ continue;
+ }
+ domain = -1; /* avoid compile-time warning */
+ s = -1; /* avoid compile-time warning */
+ if (FD_ISSET(funix, &readfds)) {
+ domain = AF_UNIX, fromlen = sizeof(fromunix);
+ s = accept(funix,
+ (struct sockaddr *)&fromunix, &fromlen);
+ } else {
+ for (i = 1; i <= *finet; i++)
+ if (FD_ISSET(finet[i], &readfds)) {
+ domain = AF_INET;
+ fromlen = sizeof(frominet);
+ s = accept(finet[i],
+ (struct sockaddr *)&frominet,
+ &fromlen);
+ }
+ }
+ if (s < 0) {
+ if (errno != EINTR)
+ syslog(LOG_WARNING, "accept: %m");
+ continue;
+ }
+ if (fork() == 0) {
+ /*
+ * Note that printjob() also plays around with
+ * signal-handling routines, and may need to be
+ * changed when making changes to signal-handling.
+ */
+ signal(SIGCHLD, SIG_DFL);
+ signal(SIGHUP, SIG_IGN);
+ signal(SIGINT, SIG_IGN);
+ signal(SIGQUIT, SIG_IGN);
+ signal(SIGTERM, SIG_IGN);
+ (void) close(funix);
+ if (sflag == 0 && finet) {
+ for (i = 1; i <= *finet; i++)
+ (void)close(finet[i]);
+ }
+ dup2(s, STDOUT_FILENO);
+ (void) close(s);
+ if (domain == AF_INET) {
+ /* for both AF_INET and AF_INET6 */
+ from_remote = 1;
+ chkhost((struct sockaddr *)&frominet,
+ ch_options);
+ } else
+ from_remote = 0;
+ doit();
+ exit(0);
+ }
+ (void) close(s);
+ }
+}
+
+static void
+reapchild(int signo __unused)
+{
+ int status;
+
+ while (wait3(&status, WNOHANG, 0) > 0)
+ ;
+}
+
+static void
+mcleanup(int signo)
+{
+ /*
+ * XXX syslog(3) is not signal-safe.
+ */
+ if (lflag) {
+ if (signo)
+ syslog(LOG_INFO, "exiting on signal %d", signo);
+ else
+ syslog(LOG_INFO, "exiting");
+ }
+ unlink(_PATH_SOCKETNAME);
+ exit(0);
+}
+
+/*
+ * Stuff for handling job specifications
+ */
+char *user[MAXUSERS]; /* users to process */
+int users; /* # of users in user array */
+int requ[MAXREQUESTS]; /* job number of spool entries */
+int requests; /* # of spool requests */
+char *person; /* name of person doing lprm */
+
+ /* buffer to hold the client's machine-name */
+static char frombuf[MAXHOSTNAMELEN];
+char cbuf[BUFSIZ]; /* command line buffer */
+const char *cmdnames[] = {
+ "null",
+ "printjob",
+ "recvjob",
+ "displayq short",
+ "displayq long",
+ "rmjob"
+};
+
+static void
+doit(void)
+{
+ char *cp, *printer;
+ int n;
+ int status;
+ struct printer myprinter, *pp = &myprinter;
+
+ init_printer(&myprinter);
+
+ for (;;) {
+ cp = cbuf;
+ do {
+ if (cp >= &cbuf[sizeof(cbuf) - 1])
+ fatal(0, "Command line too long");
+ if ((n = read(STDOUT_FILENO, cp, 1)) != 1) {
+ if (n < 0)
+ fatal(0, "Lost connection");
+ return;
+ }
+ } while (*cp++ != '\n');
+ *--cp = '\0';
+ cp = cbuf;
+ if (lflag) {
+ if (*cp >= '\1' && *cp <= '\5')
+ syslog(LOG_INFO, "%s requests %s %s",
+ from_host, cmdnames[(u_char)*cp], cp+1);
+ else
+ syslog(LOG_INFO, "bad request (%d) from %s",
+ *cp, from_host);
+ }
+ switch (*cp++) {
+ case CMD_CHECK_QUE: /* check the queue, print any jobs there */
+ startprinting(cp);
+ break;
+ case CMD_TAKE_THIS: /* receive files to be queued */
+ if (!from_remote) {
+ syslog(LOG_INFO, "illegal request (%d)", *cp);
+ exit(1);
+ }
+ recvjob(cp);
+ break;
+ case CMD_SHOWQ_SHORT: /* display the queue (short form) */
+ case CMD_SHOWQ_LONG: /* display the queue (long form) */
+ /* XXX - this all needs to be redone. */
+ printer = cp;
+ while (*cp) {
+ if (*cp != ' ') {
+ cp++;
+ continue;
+ }
+ *cp++ = '\0';
+ while (isspace(*cp))
+ cp++;
+ if (*cp == '\0')
+ break;
+ if (isdigit(*cp)) {
+ if (requests >= MAXREQUESTS)
+ fatal(0, "Too many requests");
+ requ[requests++] = atoi(cp);
+ } else {
+ if (users >= MAXUSERS)
+ fatal(0, "Too many users");
+ user[users++] = cp;
+ }
+ }
+ status = getprintcap(printer, pp);
+ if (status < 0)
+ fatal(pp, "%s", pcaperr(status));
+ displayq(pp, cbuf[0] == CMD_SHOWQ_LONG);
+ exit(0);
+ case CMD_RMJOB: /* remove a job from the queue */
+ if (!from_remote) {
+ syslog(LOG_INFO, "illegal request (%d)", *cp);
+ exit(1);
+ }
+ printer = cp;
+ while (*cp && *cp != ' ')
+ cp++;
+ if (!*cp)
+ break;
+ *cp++ = '\0';
+ person = cp;
+ while (*cp) {
+ if (*cp != ' ') {
+ cp++;
+ continue;
+ }
+ *cp++ = '\0';
+ while (isspace(*cp))
+ cp++;
+ if (*cp == '\0')
+ break;
+ if (isdigit(*cp)) {
+ if (requests >= MAXREQUESTS)
+ fatal(0, "Too many requests");
+ requ[requests++] = atoi(cp);
+ } else {
+ if (users >= MAXUSERS)
+ fatal(0, "Too many users");
+ user[users++] = cp;
+ }
+ }
+ rmjob(printer);
+ break;
+ }
+ fatal(0, "Illegal service request");
+ }
+}
+
+/*
+ * Make a pass through the printcap database and start printing any
+ * files left from the last time the machine went down.
+ */
+static void
+startup(void)
+{
+ int pid, status, more;
+ struct printer myprinter, *pp = &myprinter;
+
+ more = firstprinter(pp, &status);
+ if (status)
+ goto errloop;
+ while (more) {
+ if (ckqueue(pp) <= 0) {
+ goto next;
+ }
+ if (lflag)
+ syslog(LOG_INFO, "lpd startup: work for %s",
+ pp->printer);
+ if ((pid = fork()) < 0) {
+ syslog(LOG_WARNING, "lpd startup: cannot fork for %s",
+ pp->printer);
+ mcleanup(0);
+ }
+ if (pid == 0) {
+ lastprinter();
+ printjob(pp);
+ /* NOTREACHED */
+ }
+ do {
+next:
+ more = nextprinter(pp, &status);
+errloop:
+ if (status)
+ syslog(LOG_WARNING,
+ "lpd startup: printcap entry for %s has errors, skipping",
+ pp->printer ? pp->printer : "<noname?>");
+ } while (more && status);
+ }
+}
+
+/*
+ * Make sure there's some work to do before forking off a child
+ */
+static int
+ckqueue(struct printer *pp)
+{
+ register struct dirent *d;
+ DIR *dirp;
+ char *spooldir;
+
+ spooldir = pp->spool_dir;
+ if ((dirp = opendir(spooldir)) == NULL)
+ return (-1);
+ while ((d = readdir(dirp)) != NULL) {
+ if (d->d_name[0] != 'c' || d->d_name[1] != 'f')
+ continue; /* daemon control files only */
+ closedir(dirp);
+ return (1); /* found something */
+ }
+ closedir(dirp);
+ return (0);
+}
+
+#define DUMMY ":nobody::"
+
+/*
+ * Check to see if the host connecting to this host has access to any
+ * lpd services on this host.
+ */
+static void
+chkhost(struct sockaddr *f, int ch_opts)
+{
+ struct addrinfo hints, *res, *r;
+ register FILE *hostf;
+ char hostbuf[NI_MAXHOST], ip[NI_MAXHOST];
+ char serv[NI_MAXSERV];
+ char *syserr, *usererr;
+ int error, errsav, fpass, good;
+
+ from_host = ".na.";
+
+ /* Need real hostname for temporary filenames */
+ error = getnameinfo(f, f->sa_len, hostbuf, sizeof(hostbuf), NULL, 0,
+ NI_NAMEREQD);
+ if (error) {
+ errsav = error;
+ error = getnameinfo(f, f->sa_len, hostbuf, sizeof(hostbuf),
+ NULL, 0, NI_NUMERICHOST);
+ if (error) {
+ asprintf(&syserr,
+ "can not determine hostname for remote host (%d,%d)",
+ errsav, error);
+ asprintf(&usererr,
+ "Host name for your address is not known");
+ fhosterr(ch_opts, syserr, usererr);
+ /* NOTREACHED */
+ }
+ asprintf(&syserr,
+ "Host name for remote host (%s) not known (%d)",
+ hostbuf, errsav);
+ asprintf(&usererr,
+ "Host name for your address (%s) is not known",
+ hostbuf);
+ fhosterr(ch_opts, syserr, usererr);
+ /* NOTREACHED */
+ }
+
+ strlcpy(frombuf, hostbuf, sizeof(frombuf));
+ from_host = frombuf;
+ ch_opts |= LPD_ADDFROMLINE;
+
+ /* Need address in stringform for comparison (no DNS lookup here) */
+ error = getnameinfo(f, f->sa_len, hostbuf, sizeof(hostbuf), NULL, 0,
+ NI_NUMERICHOST);
+ if (error) {
+ asprintf(&syserr, "Cannot print IP address (error %d)",
+ error);
+ asprintf(&usererr, "Cannot print IP address for your host");
+ fhosterr(ch_opts, syserr, usererr);
+ /* NOTREACHED */
+ }
+ from_ip = strdup(hostbuf);
+
+ /* Reject numeric addresses */
+ memset(&hints, 0, sizeof(hints));
+ hints.ai_family = family;
+ hints.ai_socktype = SOCK_DGRAM; /*dummy*/
+ hints.ai_flags = AI_PASSIVE | AI_NUMERICHOST;
+ if (getaddrinfo(from_host, NULL, &hints, &res) == 0) {
+ freeaddrinfo(res);
+ /* This syslog message already includes from_host */
+ ch_opts &= ~LPD_ADDFROMLINE;
+ asprintf(&syserr, "reverse lookup results in non-FQDN %s",
+ from_host);
+ /* same message to both syslog and remote user */
+ fhosterr(ch_opts, syserr, syserr);
+ /* NOTREACHED */
+ }
+
+ /* Check for spoof, ala rlogind */
+ memset(&hints, 0, sizeof(hints));
+ hints.ai_family = family;
+ hints.ai_socktype = SOCK_DGRAM; /*dummy*/
+ error = getaddrinfo(from_host, NULL, &hints, &res);
+ if (error) {
+ asprintf(&syserr, "dns lookup for address %s failed: %s",
+ from_ip, gai_strerror(error));
+ asprintf(&usererr, "hostname for your address (%s) unknown: %s",
+ from_ip, gai_strerror(error));
+ fhosterr(ch_opts, syserr, usererr);
+ /* NOTREACHED */
+ }
+ good = 0;
+ for (r = res; good == 0 && r; r = r->ai_next) {
+ error = getnameinfo(r->ai_addr, r->ai_addrlen, ip, sizeof(ip),
+ NULL, 0, NI_NUMERICHOST);
+ if (!error && !strcmp(from_ip, ip))
+ good = 1;
+ }
+ if (res)
+ freeaddrinfo(res);
+ if (good == 0) {
+ asprintf(&syserr, "address for remote host (%s) not matched",
+ from_ip);
+ asprintf(&usererr,
+ "address for your hostname (%s) not matched", from_ip);
+ fhosterr(ch_opts, syserr, usererr);
+ /* NOTREACHED */
+ }
+
+ fpass = 1;
+ hostf = fopen(_PATH_HOSTSEQUIV, "r");
+again:
+ if (hostf) {
+ if (__ivaliduser_sa(hostf, f, f->sa_len, DUMMY, DUMMY) == 0) {
+ (void) fclose(hostf);
+ goto foundhost;
+ }
+ (void) fclose(hostf);
+ }
+ if (fpass == 1) {
+ fpass = 2;
+ hostf = fopen(_PATH_HOSTSLPD, "r");
+ goto again;
+ }
+ /* This syslog message already includes from_host */
+ ch_opts &= ~LPD_ADDFROMLINE;
+ asprintf(&syserr, "refused connection from %s, sip=%s", from_host,
+ from_ip);
+ asprintf(&usererr,
+ "Print-services are not available to your host (%s).", from_host);
+ fhosterr(ch_opts, syserr, usererr);
+ /* NOTREACHED */
+
+foundhost:
+ if (ch_opts & LPD_NOPORTCHK)
+ return; /* skip the reserved-port check */
+
+ error = getnameinfo(f, f->sa_len, NULL, 0, serv, sizeof(serv),
+ NI_NUMERICSERV);
+ if (error) {
+ /* same message to both syslog and remote user */
+ asprintf(&syserr, "malformed from-address (%d)", error);
+ fhosterr(ch_opts, syserr, syserr);
+ /* NOTREACHED */
+ }
+
+ if (atoi(serv) >= IPPORT_RESERVED) {
+ /* same message to both syslog and remote user */
+ asprintf(&syserr, "connected from invalid port (%s)", serv);
+ fhosterr(ch_opts, syserr, syserr);
+ /* NOTREACHED */
+ }
+}
+
+/*
+ * Handle fatal errors in chkhost. The first message will optionally be
+ * sent to syslog, the second one is sent to the connecting host.
+ *
+ * The idea is that the syslog message is meant for an administrator of a
+ * print server (the host receiving connections), while the usermsg is meant
+ * for a remote user who may or may not be clueful, and may or may not be
+ * doing something nefarious. Some remote users (eg, MS-Windows...) may not
+ * even see whatever message is sent, which is why there's the option to
+ * start 'lpd' with the connection-errors also sent to syslog.
+ *
+ * Given that hostnames can theoretically be fairly long (well, over 250
+ * bytes), it would probably be helpful to have the 'from_host' field at
+ * the end of any error messages which include that info.
+ *
+ * These are Fatal host-connection errors, so this routine does not return.
+ */
+static void
+fhosterr(int ch_opts, char *sysmsg, char *usermsg)
+{
+
+ /*
+ * If lpd was started up to print connection errors, then write
+ * the syslog message before the user message.
+ * And for many of the syslog messages, it is helpful to first
+ * write the from_host (if it is known) as a separate syslog
+ * message, since the hostname may be so long.
+ */
+ if (ch_opts & LPD_LOGCONNERR) {
+ if (ch_opts & LPD_ADDFROMLINE) {
+ syslog(LOG_WARNING, "for connection from %s:", from_host);
+ }
+ syslog(LOG_WARNING, "%s", sysmsg);
+ }
+
+ /*
+ * Now send the error message to the remote host which is trying
+ * to make the connection.
+ */
+ printf("%s [@%s]: %s\n", progname, local_host, usermsg);
+ fflush(stdout);
+
+ /*
+ * Add a minimal delay before exiting (and disconnecting from the
+ * sending-host). This is just in case that machine responds by
+ * INSTANTLY retrying (and instantly re-failing...). This may also
+ * give the other side more time to read the error message.
+ */
+ sleep(2); /* a paranoid throttling measure */
+ exit(1);
+}
+
+/* setup server socket for specified address family */
+/* if af is PF_UNSPEC more than one socket may be returned */
+/* the returned list is dynamically allocated, so caller needs to free it */
+static int *
+socksetup(int af, int debuglvl)
+{
+ struct addrinfo hints, *res, *r;
+ int error, maxs, *s, *socks;
+ const int on = 1;
+
+ memset(&hints, 0, sizeof(hints));
+ hints.ai_flags = AI_PASSIVE;
+ hints.ai_family = af;
+ hints.ai_socktype = SOCK_STREAM;
+ error = getaddrinfo(NULL, "printer", &hints, &res);
+ if (error) {
+ syslog(LOG_ERR, "%s", gai_strerror(error));
+ mcleanup(0);
+ }
+
+ /* Count max number of sockets we may open */
+ for (maxs = 0, r = res; r; r = r->ai_next, maxs++)
+ ;
+ socks = malloc((maxs + 1) * sizeof(int));
+ if (!socks) {
+ syslog(LOG_ERR, "couldn't allocate memory for sockets");
+ mcleanup(0);
+ }
+
+ *socks = 0; /* num of sockets counter at start of array */
+ s = socks + 1;
+ for (r = res; r; r = r->ai_next) {
+ *s = socket(r->ai_family, r->ai_socktype, r->ai_protocol);
+ if (*s < 0) {
+ syslog(LOG_DEBUG, "socket(): %m");
+ continue;
+ }
+ if (setsockopt(*s, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on))
+ < 0) {
+ syslog(LOG_ERR, "setsockopt(SO_REUSEADDR): %m");
+ close(*s);
+ continue;
+ }
+ if (debuglvl)
+ if (setsockopt(*s, SOL_SOCKET, SO_DEBUG, &debuglvl,
+ sizeof(debuglvl)) < 0) {
+ syslog(LOG_ERR, "setsockopt (SO_DEBUG): %m");
+ close(*s);
+ continue;
+ }
+ if (r->ai_family == AF_INET6) {
+ if (setsockopt(*s, IPPROTO_IPV6, IPV6_V6ONLY,
+ &on, sizeof(on)) < 0) {
+ syslog(LOG_ERR,
+ "setsockopt (IPV6_V6ONLY): %m");
+ close(*s);
+ continue;
+ }
+ }
+ if (bind(*s, r->ai_addr, r->ai_addrlen) < 0) {
+ syslog(LOG_DEBUG, "bind(): %m");
+ close(*s);
+ continue;
+ }
+ (*socks)++;
+ s++;
+ }
+
+ if (res)
+ freeaddrinfo(res);
+
+ if (*socks == 0) {
+ syslog(LOG_ERR, "Couldn't bind to any socket");
+ free(socks);
+ mcleanup(0);
+ }
+ return(socks);
+}
+
+static void
+usage(void)
+{
+#ifdef INET6
+ fprintf(stderr, "usage: lpd [-cdlsFW46] [port#]\n");
+#else
+ fprintf(stderr, "usage: lpd [-cdlsFW] [port#]\n");
+#endif
+ exit(EX_USAGE);
+}
diff --git a/usr.sbin/lpr/lpd/lpdchar.c b/usr.sbin/lpr/lpd/lpdchar.c
new file mode 100644
index 000000000000..a9d06fe7f600
--- /dev/null
+++ b/usr.sbin/lpr/lpd/lpdchar.c
@@ -0,0 +1,1062 @@
+/*-
+ * SPDX-License-Identifier: BSD-3-Clause
+ *
+ * Copyright (c) 1983, 1993
+ * The Regents of the University of California. All rights reserved.
+ *
+ * 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 "lp.cdefs.h" /* A cross-platform version of <sys/cdefs.h> */
+/*
+ * Character set for line printer daemon
+ */
+#include "lp.local.h"
+#include "extern.h"
+
+#define c_______ 0
+#define c______1 01
+#define c_____1_ 02
+#define c____1__ 04
+#define c____11_ 06
+#define c___1___ 010
+#define c___1__1 011
+#define c___1_1_ 012
+#define c___11__ 014
+#define c__1____ 020
+#define c__1__1_ 022
+#define c__1_1__ 024
+#define c__11___ 030
+#define c__111__ 034
+#define c__111_1 035
+#define c__1111_ 036
+#define c__11111 037
+#define c_1_____ 040
+#define c_1____1 041
+#define c_1___1_ 042
+#define c_1__1__ 044
+#define c_1_1___ 050
+#define c_1_1__1 051
+#define c_1_1_1_ 052
+#define c_11____ 060
+#define c_11_11_ 066
+#define c_111___ 070
+#define c_111__1 071
+#define c_111_1_ 072
+#define c_1111__ 074
+#define c_1111_1 075
+#define c_11111_ 076
+#define c_111111 077
+#define c1______ 0100
+#define c1_____1 0101
+#define c1____1_ 0102
+#define c1____11 0103
+#define c1___1__ 0104
+#define c1___1_1 0105
+#define c1___11_ 0106
+#define c1__1___ 0110
+#define c1__1__1 0111
+#define c1__11_1 0115
+#define c1__1111 0117
+#define c1_1____ 0120
+#define c1_1___1 0121
+#define c1_1_1_1 0125
+#define c1_1_11_ 0126
+#define c1_111__ 0134
+#define c1_1111_ 0136
+#define c11____1 0141
+#define c11___1_ 0142
+#define c11___11 0143
+#define c11_1___ 0150
+#define c11_1__1 0151
+#define c111_11_ 0166
+#define c1111___ 0170
+#define c11111__ 0174
+#define c111111_ 0176
+#define c1111111 0177
+
+char scnkey[][HEIGHT] = /* this is relatively easy to modify */
+ /* just look: */
+{
+ { c_______,
+ c_______,
+ c_______,
+ c_______,
+ c_______,
+ c_______,
+ c_______,
+ c_______,
+ c_______ }, /* */
+
+ { c__11___,
+ c__11___,
+ c__11___,
+ c__11___,
+ c__11___,
+ c_______,
+ c_______,
+ c__11___,
+ c__11___ }, /* ! */
+
+ { c_1__1__,
+ c_1__1__,
+ c_______,
+ c_______,
+ c_______,
+ c_______,
+ c_______,
+ c_______,
+ c_______ }, /* " */
+
+ { c_______,
+ c__1_1__,
+ c__1_1__,
+ c1111111,
+ c__1_1__,
+ c1111111,
+ c__1_1__,
+ c__1_1__,
+ c_______ }, /* # */
+
+ { c___1___,
+ c_11111_,
+ c1__1__1,
+ c1__1___,
+ c_11111_,
+ c___1__1,
+ c1__1__1,
+ c_11111_,
+ c___1___ }, /* $ */
+
+ { c_1_____,
+ c1_1___1,
+ c_1___1_,
+ c____1__,
+ c___1___,
+ c__1____,
+ c_1___1_,
+ c1___1_1,
+ c_____1_ }, /* % */
+
+ { c_11____,
+ c1__1___,
+ c1___1__,
+ c_1_1___,
+ c__1____,
+ c_1_1__1,
+ c1___11_,
+ c1___11_,
+ c_111__1 }, /* & */
+
+ { c___11__,
+ c___11__,
+ c___1___,
+ c__1____,
+ c_______,
+ c_______,
+ c_______,
+ c_______,
+ c_______ }, /* ' */
+
+ { c____1__,
+ c___1___,
+ c__1____,
+ c__1____,
+ c__1____,
+ c__1____,
+ c__1____,
+ c___1___,
+ c____1__ }, /* ( */
+
+ { c__1____,
+ c___1___,
+ c____1__,
+ c____1__,
+ c____1__,
+ c____1__,
+ c____1__,
+ c___1___,
+ c__1____ }, /* ) */
+
+ { c_______,
+ c___1___,
+ c1__1__1,
+ c_1_1_1_,
+ c__111__,
+ c_1_1_1_,
+ c1__1__1,
+ c___1___,
+ c_______ }, /* * */
+
+ { c_______,
+ c___1___,
+ c___1___,
+ c___1___,
+ c1111111,
+ c___1___,
+ c___1___,
+ c___1___,
+ c_______ }, /* + */
+
+ { c_______,
+ c_______,
+ c_______,
+ c_______,
+ c__11___,
+ c__11___,
+ c__1____,
+ c_1_____,
+ c_______ }, /* , */
+
+ { c_______,
+ c_______,
+ c_______,
+ c_______,
+ c1111111,
+ c_______,
+ c_______,
+ c_______,
+ c_______ }, /* - */
+
+ { c_______,
+ c_______,
+ c_______,
+ c_______,
+ c_______,
+ c_______,
+ c_______,
+ c__11___,
+ c__11___ }, /* . */
+
+ { c_______,
+ c______1,
+ c_____1_,
+ c____1__,
+ c___1___,
+ c__1____,
+ c_1_____,
+ c1______,
+ c_______ }, /* / */
+
+ { c_11111_,
+ c1_____1,
+ c1____11,
+ c1___1_1,
+ c1__1__1,
+ c1_1___1,
+ c11____1,
+ c1_____1,
+ c_11111_ }, /* 0 */
+
+ { c___1___,
+ c__11___,
+ c_1_1___,
+ c___1___,
+ c___1___,
+ c___1___,
+ c___1___,
+ c___1___,
+ c_11111_ }, /* 1 */
+
+ { c_11111_,
+ c1_____1,
+ c______1,
+ c_____1_,
+ c__111__,
+ c_1_____,
+ c1______,
+ c1______,
+ c1111111 }, /* 2 */
+
+ { c_11111_,
+ c1_____1,
+ c______1,
+ c______1,
+ c__1111_,
+ c______1,
+ c______1,
+ c1_____1,
+ c_11111_ }, /* 3 */
+
+ { c_____1_,
+ c____11_,
+ c___1_1_,
+ c__1__1_,
+ c_1___1_,
+ c1____1_,
+ c1111111,
+ c_____1_,
+ c_____1_ }, /* 4 */
+
+ { c1111111,
+ c1______,
+ c1______,
+ c11111__,
+ c_____1_,
+ c______1,
+ c______1,
+ c1____1_,
+ c_1111__ }, /* 5 */
+
+ { c__1111_,
+ c_1_____,
+ c1______,
+ c1______,
+ c1_1111_,
+ c11____1,
+ c1_____1,
+ c1_____1,
+ c_11111_ }, /* 6 */
+
+ { c1111111,
+ c1_____1,
+ c_____1_,
+ c____1__,
+ c___1___,
+ c__1____,
+ c__1____,
+ c__1____,
+ c__1____ }, /* 7 */
+
+ { c_11111_,
+ c1_____1,
+ c1_____1,
+ c1_____1,
+ c_11111_,
+ c1_____1,
+ c1_____1,
+ c1_____1,
+ c_11111_ }, /* 8 */
+
+ { c_11111_,
+ c1_____1,
+ c1_____1,
+ c1_____1,
+ c_111111,
+ c______1,
+ c______1,
+ c1_____1,
+ c_1111__ }, /* 9 */
+
+ { c_______,
+ c_______,
+ c_______,
+ c__11___,
+ c__11___,
+ c_______,
+ c_______,
+ c__11___,
+ c__11___ }, /* : */
+
+
+ { c__11___,
+ c__11___,
+ c_______,
+ c_______,
+ c__11___,
+ c__11___,
+ c__1____,
+ c_1_____,
+ c_______ }, /* ; */
+
+ { c____1__,
+ c___1___,
+ c__1____,
+ c_1_____,
+ c1______,
+ c_1_____,
+ c__1____,
+ c___1___,
+ c____1__ }, /* < */
+
+ { c_______,
+ c_______,
+ c_______,
+ c1111111,
+ c_______,
+ c1111111,
+ c_______,
+ c_______,
+ c_______ }, /* = */
+
+ { c__1____,
+ c___1___,
+ c____1__,
+ c_____1_,
+ c______1,
+ c_____1_,
+ c____1__,
+ c___1___,
+ c__1____ }, /* > */
+
+ { c__1111_,
+ c_1____1,
+ c_1____1,
+ c______1,
+ c____11_,
+ c___1___,
+ c___1___,
+ c_______,
+ c___1___ }, /* ? */
+
+ { c__1111_,
+ c_1____1,
+ c1__11_1,
+ c1_1_1_1,
+ c1_1_1_1,
+ c1_1111_,
+ c1______,
+ c_1____1,
+ c__1111_ }, /* @ */
+
+ { c__111__,
+ c_1___1_,
+ c1_____1,
+ c1_____1,
+ c1111111,
+ c1_____1,
+ c1_____1,
+ c1_____1,
+ c1_____1 }, /* A */
+
+ { c111111_,
+ c_1____1,
+ c_1____1,
+ c_1____1,
+ c_11111_,
+ c_1____1,
+ c_1____1,
+ c_1____1,
+ c111111_ }, /* B */
+
+ { c__1111_,
+ c_1____1,
+ c1______,
+ c1______,
+ c1______,
+ c1______,
+ c1______,
+ c_1____1,
+ c__1111_ }, /* C */
+
+ { c11111__,
+ c_1___1_,
+ c_1____1,
+ c_1____1,
+ c_1____1,
+ c_1____1,
+ c_1____1,
+ c_1___1_,
+ c11111__ }, /* D */
+
+ { c1111111,
+ c1______,
+ c1______,
+ c1______,
+ c111111_,
+ c1______,
+ c1______,
+ c1______,
+ c1111111 }, /* E */
+
+ { c1111111,
+ c1______,
+ c1______,
+ c1______,
+ c111111_,
+ c1______,
+ c1______,
+ c1______,
+ c1______ }, /* F */
+
+ { c__1111_,
+ c_1____1,
+ c1______,
+ c1______,
+ c1______,
+ c1__1111,
+ c1_____1,
+ c_1____1,
+ c__1111_ }, /* G */
+
+ { c1_____1,
+ c1_____1,
+ c1_____1,
+ c1_____1,
+ c1111111,
+ c1_____1,
+ c1_____1,
+ c1_____1,
+ c1_____1 }, /* H */
+
+ { c_11111_,
+ c___1___,
+ c___1___,
+ c___1___,
+ c___1___,
+ c___1___,
+ c___1___,
+ c___1___,
+ c_11111_ }, /* I */
+
+ { c__11111,
+ c____1__,
+ c____1__,
+ c____1__,
+ c____1__,
+ c____1__,
+ c____1__,
+ c1___1__,
+ c_111___ }, /* J */
+
+ { c1_____1,
+ c1____1_,
+ c1___1__,
+ c1__1___,
+ c1_1____,
+ c11_1___,
+ c1___1__,
+ c1____1_,
+ c1_____1 }, /* K */
+
+ { c1______,
+ c1______,
+ c1______,
+ c1______,
+ c1______,
+ c1______,
+ c1______,
+ c1______,
+ c1111111 }, /* L */
+
+ { c1_____1,
+ c11___11,
+ c1_1_1_1,
+ c1__1__1,
+ c1_____1,
+ c1_____1,
+ c1_____1,
+ c1_____1,
+ c1_____1 }, /* M */
+
+ { c1_____1,
+ c11____1,
+ c1_1___1,
+ c1__1__1,
+ c1___1_1,
+ c1____11,
+ c1_____1,
+ c1_____1,
+ c1_____1 }, /* N */
+
+ { c__111__,
+ c_1___1_,
+ c1_____1,
+ c1_____1,
+ c1_____1,
+ c1_____1,
+ c1_____1,
+ c_1___1_,
+ c__111__ }, /* O */
+
+ { c111111_,
+ c1_____1,
+ c1_____1,
+ c1_____1,
+ c111111_,
+ c1______,
+ c1______,
+ c1______,
+ c1______ }, /* P */
+
+ { c__111__,
+ c_1___1_,
+ c1_____1,
+ c1_____1,
+ c1_____1,
+ c1__1__1,
+ c1___1_1,
+ c_1___1_,
+ c__111_1 }, /* Q */
+
+ { c111111_,
+ c1_____1,
+ c1_____1,
+ c1_____1,
+ c111111_,
+ c1__1___,
+ c1___1__,
+ c1____1_,
+ c1_____1 }, /* R */
+
+ { c_11111_,
+ c1_____1,
+ c1______,
+ c1______,
+ c_11111_,
+ c______1,
+ c______1,
+ c1_____1,
+ c_11111_ }, /* S */
+
+ { c1111111,
+ c___1___,
+ c___1___,
+ c___1___,
+ c___1___,
+ c___1___,
+ c___1___,
+ c___1___,
+ c___1___ }, /* T */
+
+ { c1_____1,
+ c1_____1,
+ c1_____1,
+ c1_____1,
+ c1_____1,
+ c1_____1,
+ c1_____1,
+ c1_____1,
+ c_11111_ }, /* U */
+
+ { c1_____1,
+ c1_____1,
+ c1_____1,
+ c_1___1_,
+ c_1___1_,
+ c__1_1__,
+ c__1_1__,
+ c___1___,
+ c___1___ }, /* V */
+
+ { c1_____1,
+ c1_____1,
+ c1_____1,
+ c1_____1,
+ c1__1__1,
+ c1__1__1,
+ c1_1_1_1,
+ c11___11,
+ c1_____1 }, /* W */
+
+ { c1_____1,
+ c1_____1,
+ c_1___1_,
+ c__1_1__,
+ c___1___,
+ c__1_1__,
+ c_1___1_,
+ c1_____1,
+ c1_____1 }, /* X */
+
+ { c1_____1,
+ c1_____1,
+ c_1___1_,
+ c__1_1__,
+ c___1___,
+ c___1___,
+ c___1___,
+ c___1___,
+ c___1___ }, /* Y */
+
+ { c1111111,
+ c______1,
+ c_____1_,
+ c____1__,
+ c___1___,
+ c__1____,
+ c_1_____,
+ c1______,
+ c1111111 }, /* Z */
+
+ { c_1111__,
+ c_1_____,
+ c_1_____,
+ c_1_____,
+ c_1_____,
+ c_1_____,
+ c_1_____,
+ c_1_____,
+ c_1111__ }, /* [ */
+
+ { c_______,
+ c1______,
+ c_1_____,
+ c__1____,
+ c___1___,
+ c____1__,
+ c_____1_,
+ c______1,
+ c_______ }, /* \ */
+
+ { c__1111_,
+ c_____1_,
+ c_____1_,
+ c_____1_,
+ c_____1_,
+ c_____1_,
+ c_____1_,
+ c_____1_,
+ c__1111_ }, /* ] */
+
+ { c___1___,
+ c__1_1__,
+ c_1___1_,
+ c1_____1,
+ c_______,
+ c_______,
+ c_______,
+ c_______ }, /* ^ */
+
+ { c_______,
+ c_______,
+ c_______,
+ c_______,
+ c_______,
+ c_______,
+ c_______,
+ c1111111,
+ c_______ }, /* _ */
+
+ { c__11___,
+ c__11___,
+ c___1___,
+ c____1__,
+ c_______,
+ c_______,
+ c_______,
+ c_______,
+ c_______ }, /* ` */
+
+ { c_______,
+ c_______,
+ c_______,
+ c_1111__,
+ c_____1_,
+ c_11111_,
+ c1_____1,
+ c1____11,
+ c_1111_1 }, /* a */
+
+ { c1______,
+ c1______,
+ c1______,
+ c1_111__,
+ c11___1_,
+ c1_____1,
+ c1_____1,
+ c11___1_,
+ c1_111__ }, /* b */
+
+ { c_______,
+ c_______,
+ c_______,
+ c_1111__,
+ c1____1_,
+ c1______,
+ c1______,
+ c1____1_,
+ c_1111__ }, /* c */
+
+ { c_____1_,
+ c_____1_,
+ c_____1_,
+ c_111_1_,
+ c1___11_,
+ c1____1_,
+ c1____1_,
+ c1___11_,
+ c_111_1_ }, /* d */
+
+ { c_______,
+ c_______,
+ c_______,
+ c_1111__,
+ c1____1_,
+ c111111_,
+ c1______,
+ c1____1_,
+ c_1111__ }, /* e */
+
+ { c___11__,
+ c__1__1_,
+ c__1____,
+ c__1____,
+ c11111__,
+ c__1____,
+ c__1____,
+ c__1____,
+ c__1____ }, /* f */
+
+ { c_111_1_,
+ c1___11_,
+ c1____1_,
+ c1____1_,
+ c1___11_,
+ c_111_1_,
+ c_____1_,
+ c1____1_,
+ c_1111__ }, /* g */
+
+ { c1______,
+ c1______,
+ c1______,
+ c1_111__,
+ c11___1_,
+ c1____1_,
+ c1____1_,
+ c1____1_,
+ c1____1_ }, /* h */
+
+ { c_______,
+ c___1___,
+ c_______,
+ c__11___,
+ c___1___,
+ c___1___,
+ c___1___,
+ c___1___,
+ c__111__ }, /* i */
+
+ { c____11_,
+ c_____1_,
+ c_____1_,
+ c_____1_,
+ c_____1_,
+ c_____1_,
+ c_____1_,
+ c_1___1_,
+ c__111__ }, /* j */
+
+ { c1______,
+ c1______,
+ c1______,
+ c1___1__,
+ c1__1___,
+ c1_1____,
+ c11_1___,
+ c1___1__,
+ c1____1_ }, /* k */
+
+ { c__11___,
+ c___1___,
+ c___1___,
+ c___1___,
+ c___1___,
+ c___1___,
+ c___1___,
+ c___1___,
+ c__111__ }, /* l */
+
+ { c_______,
+ c_______,
+ c_______,
+ c1_1_11_,
+ c11_1__1,
+ c1__1__1,
+ c1__1__1,
+ c1__1__1,
+ c1__1__1 }, /* m */
+
+ { c_______,
+ c_______,
+ c_______,
+ c1_111__,
+ c11___1_,
+ c1____1_,
+ c1____1_,
+ c1____1_,
+ c1____1_ }, /* n */
+
+ { c_______,
+ c_______,
+ c_______,
+ c_1111__,
+ c1____1_,
+ c1____1_,
+ c1____1_,
+ c1____1_,
+ c_1111__ }, /* o */
+
+ { c1_111__,
+ c11___1_,
+ c1____1_,
+ c1____1_,
+ c11___1_,
+ c1_111__,
+ c1______,
+ c1______,
+ c1______ }, /* p */
+
+ { c_111_1_,
+ c1___11_,
+ c1____1_,
+ c1____1_,
+ c1___11_,
+ c_111_1_,
+ c_____1_,
+ c_____1_,
+ c_____1_ }, /* q */
+
+ { c_______,
+ c_______,
+ c_______,
+ c1_111__,
+ c11___1_,
+ c1______,
+ c1______,
+ c1______,
+ c1______ }, /* r */
+
+ { c_______,
+ c_______,
+ c_______,
+ c_1111__,
+ c1____1_,
+ c_11____,
+ c___11__,
+ c1____1_,
+ c_1111__ }, /* s */
+
+ { c_______,
+ c__1____,
+ c__1____,
+ c11111__,
+ c__1____,
+ c__1____,
+ c__1____,
+ c__1__1_,
+ c___11__ }, /* t */
+
+ { c_______,
+ c_______,
+ c_______,
+ c1____1_,
+ c1____1_,
+ c1____1_,
+ c1____1_,
+ c1___11_,
+ c_111_1_ }, /* u */
+
+ { c_______,
+ c_______,
+ c_______,
+ c1_____1,
+ c1_____1,
+ c1_____1,
+ c_1___1_,
+ c__1_1__,
+ c___1___ }, /* v */
+
+ { c_______,
+ c_______,
+ c_______,
+ c1_____1,
+ c1__1__1,
+ c1__1__1,
+ c1__1__1,
+ c1__1__1,
+ c_11_11_ }, /* w */
+
+ { c_______,
+ c_______,
+ c_______,
+ c1____1_,
+ c_1__1__,
+ c__11___,
+ c__11___,
+ c_1__1__,
+ c1____1_ }, /* x */
+
+ { c1____1_,
+ c1____1_,
+ c1____1_,
+ c1____1_,
+ c1___11_,
+ c_111_1_,
+ c_____1_,
+ c1____1_,
+ c_1111__ }, /* y */
+
+ { c_______,
+ c_______,
+ c_______,
+ c111111_,
+ c____1__,
+ c___1___,
+ c__1____,
+ c_1_____,
+ c111111_ }, /* z */
+
+ { c___11__,
+ c__1____,
+ c__1____,
+ c__1____,
+ c_1_____,
+ c__1____,
+ c__1____,
+ c__1____,
+ c___11__ }, /* } */
+
+ { c___1___,
+ c___1___,
+ c___1___,
+ c___1___,
+ c___1___,
+ c___1___,
+ c___1___,
+ c___1___,
+ c___1___ }, /* | */
+
+ { c__11___,
+ c____1__,
+ c____1__,
+ c____1__,
+ c_____1_,
+ c____1__,
+ c____1__,
+ c____1__,
+ c__11___ }, /* } */
+
+ { c_11____,
+ c1__1__1,
+ c____11_,
+ c_______,
+ c_______,
+ c_______,
+ c_______,
+ c_______,
+ c_______ }, /* ~ */
+
+ { c_1__1__,
+ c1__1__1,
+ c__1__1_,
+ c_1__1__,
+ c1__1__1,
+ c__1__1_,
+ c_1__1__,
+ c1__1__1,
+ c__1__1_ } /* rub-out */
+};
diff --git a/usr.sbin/lpr/lpd/modes.c b/usr.sbin/lpr/lpd/modes.c
new file mode 100644
index 000000000000..c1f61d5be069
--- /dev/null
+++ b/usr.sbin/lpr/lpd/modes.c
@@ -0,0 +1,225 @@
+/*-
+ * SPDX-License-Identifier: BSD-3-Clause
+ *
+ * Copyright (c) 1991, 1993, 1994
+ * The Regents of the University of California. All rights reserved.
+ *
+ * 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 "lp.cdefs.h" /* A cross-platform version of <sys/cdefs.h> */
+#include <stddef.h>
+#include <string.h>
+#include <termios.h>
+#include "lp.local.h"
+#include "extern.h"
+
+struct modes {
+ const char *name;
+ const long set;
+ const long unset;
+};
+
+/*
+ * The code in optlist() depends on minus options following regular
+ * options, i.e. "foo" must immediately precede "-foo".
+ */
+struct modes cmodes[] = {
+ { "cs5", CS5, CSIZE },
+ { "cs6", CS6, CSIZE },
+ { "cs7", CS7, CSIZE },
+ { "cs8", CS8, CSIZE },
+ { "cstopb", CSTOPB, 0 },
+ { "-cstopb", 0, CSTOPB },
+ { "cread", CREAD, 0 },
+ { "-cread", 0, CREAD },
+ { "parenb", PARENB, 0 },
+ { "-parenb", 0, PARENB },
+ { "parodd", PARODD, 0 },
+ { "-parodd", 0, PARODD },
+ { "parity", PARENB | CS7, PARODD | CSIZE },
+ { "-parity", CS8, PARODD | PARENB | CSIZE },
+ { "evenp", PARENB | CS7, PARODD | CSIZE },
+ { "-evenp", CS8, PARODD | PARENB | CSIZE },
+ { "oddp", PARENB | CS7 | PARODD, CSIZE },
+ { "-oddp", CS8, PARODD | PARENB | CSIZE },
+ { "pass8", CS8, PARODD | PARENB | CSIZE },
+ { "-pass8", PARENB | CS7, PARODD | CSIZE },
+ { "hupcl", HUPCL, 0 },
+ { "-hupcl", 0, HUPCL },
+ { "hup", HUPCL, 0 },
+ { "-hup", 0, HUPCL },
+ { "clocal", CLOCAL, 0 },
+ { "-clocal", 0, CLOCAL },
+ { "crtscts", CRTSCTS, 0 },
+ { "-crtscts", 0, CRTSCTS },
+ { "ctsflow", CCTS_OFLOW, 0 },
+ { "-ctsflow", 0, CCTS_OFLOW },
+ { "dsrflow", CDSR_OFLOW, 0 },
+ { "-dsrflow", 0, CDSR_OFLOW },
+ { "dtrflow", CDTR_IFLOW, 0 },
+ { "-dtrflow", 0, CDTR_IFLOW },
+ { "rtsflow", CRTS_IFLOW, 0 },
+ { "-rtsflow", 0, CRTS_IFLOW },
+ { "mdmbuf", MDMBUF, 0 },
+ { "-mdmbuf", 0, MDMBUF },
+ { NULL, 0, 0},
+};
+
+struct modes imodes[] = {
+ { "ignbrk", IGNBRK, 0 },
+ { "-ignbrk", 0, IGNBRK },
+ { "brkint", BRKINT, 0 },
+ { "-brkint", 0, BRKINT },
+ { "ignpar", IGNPAR, 0 },
+ { "-ignpar", 0, IGNPAR },
+ { "parmrk", PARMRK, 0 },
+ { "-parmrk", 0, PARMRK },
+ { "inpck", INPCK, 0 },
+ { "-inpck", 0, INPCK },
+ { "istrip", ISTRIP, 0 },
+ { "-istrip", 0, ISTRIP },
+ { "inlcr", INLCR, 0 },
+ { "-inlcr", 0, INLCR },
+ { "igncr", IGNCR, 0 },
+ { "-igncr", 0, IGNCR },
+ { "icrnl", ICRNL, 0 },
+ { "-icrnl", 0, ICRNL },
+ { "ixon", IXON, 0 },
+ { "-ixon", 0, IXON },
+ { "flow", IXON, 0 },
+ { "-flow", 0, IXON },
+ { "ixoff", IXOFF, 0 },
+ { "-ixoff", 0, IXOFF },
+ { "tandem", IXOFF, 0 },
+ { "-tandem", 0, IXOFF },
+ { "ixany", IXANY, 0 },
+ { "-ixany", 0, IXANY },
+ { "decctlq", 0, IXANY },
+ { "-decctlq", IXANY, 0 },
+ { "imaxbel", IMAXBEL, 0 },
+ { "-imaxbel", 0, IMAXBEL },
+ { NULL, 0, 0},
+};
+
+struct modes lmodes[] = {
+ { "echo", ECHO, 0 },
+ { "-echo", 0, ECHO },
+ { "echoe", ECHOE, 0 },
+ { "-echoe", 0, ECHOE },
+ { "crterase", ECHOE, 0 },
+ { "-crterase", 0, ECHOE },
+ { "crtbs", ECHOE, 0 }, /* crtbs not supported, close enough */
+ { "-crtbs", 0, ECHOE },
+ { "echok", ECHOK, 0 },
+ { "-echok", 0, ECHOK },
+ { "echoke", ECHOKE, 0 },
+ { "-echoke", 0, ECHOKE },
+ { "crtkill", ECHOKE, 0 },
+ { "-crtkill", 0, ECHOKE },
+ { "altwerase", ALTWERASE, 0 },
+ { "-altwerase", 0, ALTWERASE },
+ { "iexten", IEXTEN, 0 },
+ { "-iexten", 0, IEXTEN },
+ { "echonl", ECHONL, 0 },
+ { "-echonl", 0, ECHONL },
+ { "echoctl", ECHOCTL, 0 },
+ { "-echoctl", 0, ECHOCTL },
+ { "ctlecho", ECHOCTL, 0 },
+ { "-ctlecho", 0, ECHOCTL },
+ { "echoprt", ECHOPRT, 0 },
+ { "-echoprt", 0, ECHOPRT },
+ { "prterase", ECHOPRT, 0 },
+ { "-prterase", 0, ECHOPRT },
+ { "isig", ISIG, 0 },
+ { "-isig", 0, ISIG },
+ { "icanon", ICANON, 0 },
+ { "-icanon", 0, ICANON },
+ { "noflsh", NOFLSH, 0 },
+ { "-noflsh", 0, NOFLSH },
+ { "tostop", TOSTOP, 0 },
+ { "-tostop", 0, TOSTOP },
+ { "flusho", FLUSHO, 0 },
+ { "-flusho", 0, FLUSHO },
+ { "pendin", PENDIN, 0 },
+ { "-pendin", 0, PENDIN },
+ { "crt", ECHOE|ECHOKE|ECHOCTL, ECHOK|ECHOPRT },
+ { "-crt", ECHOK, ECHOE|ECHOKE|ECHOCTL },
+ { "newcrt", ECHOE|ECHOKE|ECHOCTL, ECHOK|ECHOPRT },
+ { "-newcrt", ECHOK, ECHOE|ECHOKE|ECHOCTL },
+ { "nokerninfo", NOKERNINFO, 0 },
+ { "-nokerninfo",0, NOKERNINFO },
+ { "kerninfo", 0, NOKERNINFO },
+ { "-kerninfo", NOKERNINFO, 0 },
+ { NULL, 0, 0},
+};
+
+struct modes omodes[] = {
+ { "opost", OPOST, 0 },
+ { "-opost", 0, OPOST },
+ { "litout", 0, OPOST },
+ { "-litout", OPOST, 0 },
+ { "onlcr", ONLCR, 0 },
+ { "-onlcr", 0, ONLCR },
+ { "tabs", 0, OXTABS }, /* "preserve" tabs */
+ { "-tabs", OXTABS, 0 },
+ { "oxtabs", OXTABS, 0 },
+ { "-oxtabs", 0, OXTABS },
+ { NULL, 0, 0},
+};
+
+#define CHK(name, s) (*name == s[0] && !strcmp(name, s))
+
+int
+msearch(char *str, struct termios *ip)
+{
+ struct modes *mp;
+
+ for (mp = cmodes; mp->name; ++mp)
+ if (CHK(str, mp->name)) {
+ ip->c_cflag &= ~mp->unset;
+ ip->c_cflag |= mp->set;
+ return (1);
+ }
+ for (mp = imodes; mp->name; ++mp)
+ if (CHK(str, mp->name)) {
+ ip->c_iflag &= ~mp->unset;
+ ip->c_iflag |= mp->set;
+ return (1);
+ }
+ for (mp = lmodes; mp->name; ++mp)
+ if (CHK(str, mp->name)) {
+ ip->c_lflag &= ~mp->unset;
+ ip->c_lflag |= mp->set;
+ return (1);
+ }
+ for (mp = omodes; mp->name; ++mp)
+ if (CHK(str, mp->name)) {
+ ip->c_oflag &= ~mp->unset;
+ ip->c_oflag |= mp->set;
+ return (1);
+ }
+ return (0);
+}
diff --git a/usr.sbin/lpr/lpd/printcap b/usr.sbin/lpr/lpd/printcap
new file mode 100644
index 000000000000..ace92b2bf955
--- /dev/null
+++ b/usr.sbin/lpr/lpd/printcap
@@ -0,0 +1,52 @@
+
+#
+# This enables a simple local "raw" printer, hooked up to the first
+# parallel port. No kind of filtering is done, so everything you pass
+# to the "lpr" command will be printed unmodified.
+#
+# Remember, for further print queues you're going to add, you have
+# to choose different spool directories (the "sd" capability below),
+# otherwise you will greatly confuse lpd.
+#
+# For some advanced printing, have a look at the "apsfilter" package.
+# It plugs into the lpd system, allowing you to print a variety of
+# different file types by converting everything to PostScript(tm)
+# format. For more information about apsfilter visit
+#
+# http://www.apsfilter.org/
+#
+# If you don't have a PostScript(tm) printer, don't panic, but do
+# also install the latest "ghostscript" package for best printer support.
+#
+# Do also refer to the "printing" section of the handbook.
+#
+# https://docs.freebsd.org/en/books/handbook/printing/
+#
+# A local copy can be found under
+#
+# /usr/share/doc/handbook/handbook.{html,latin1}.
+#
+# Banner pages are now suppressed by default. Remove the :sh: capability
+# to turn them back on.
+#
+#lp|local line printer:\
+# :sh:\
+# :lp=/dev/lpt0:sd=/var/spool/output/lpd:lf=/var/log/lpd-errs:
+#
+# Sample remote printer. The physical printer is on machine "lphost".
+# You can perform any kind of local filtering directly. If you need
+# local filters (e.g. LF -> CR-LF conversion for HP printers), create
+# a filter script that sends the proper escape sequence to the printer
+# and then concatenates stdin to stdout.
+#
+#remote|sample remote printer:\
+# :sh:\
+# :rm=lphost:sd=/var/spool/output/lphost:lf=/var/log/lpd-errs:\
+# :if=/usr/local/libexec/if-script:
+#
+# Simple Russian printer with hardware CP866 character set, output filter
+# used for KOI8-R -> CP866 conversion
+#
+#lp|Russian local line printer:\
+# :sh:of=/usr/libexec/lpr/ru/koi2alt:\
+# :lp=/dev/lpt0:sd=/var/spool/output/lpd:lf=/var/log/lpd-errs:
diff --git a/usr.sbin/lpr/lpd/printjob.c b/usr.sbin/lpr/lpd/printjob.c
new file mode 100644
index 000000000000..1c6736097492
--- /dev/null
+++ b/usr.sbin/lpr/lpd/printjob.c
@@ -0,0 +1,2008 @@
+/*-
+ * SPDX-License-Identifier: BSD-3-Clause
+ *
+ * Copyright (c) 1983, 1993
+ * The Regents of the University of California. All rights reserved.
+ *
+ *
+ * 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 "lp.cdefs.h" /* A cross-platform version of <sys/cdefs.h> */
+/*
+ * printjob -- print jobs in the queue.
+ *
+ * NOTE: the lock file is used to pass information to lpq and lprm.
+ * it does not need to be removed because file locks are dynamic.
+ */
+
+#include <sys/param.h>
+#include <sys/wait.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+
+#include <pwd.h>
+#include <unistd.h>
+#include <signal.h>
+#include <syslog.h>
+#include <fcntl.h>
+#include <dirent.h>
+#include <err.h>
+#include <errno.h>
+#include <inttypes.h>
+#include <stdio.h>
+#include <string.h>
+#include <stdlib.h>
+#include <sys/ioctl.h>
+#include <termios.h>
+#include <time.h>
+#include "lp.h"
+#include "lp.local.h"
+#include "pathnames.h"
+#include "extern.h"
+
+#define DORETURN 0 /* dofork should return "can't fork" error */
+#define DOABORT 1 /* dofork should just die if fork() fails */
+
+/*
+ * The buffer size to use when reading/writing spool files.
+ */
+#define SPL_BUFSIZ BUFSIZ
+
+/*
+ * Error tokens
+ */
+#define REPRINT -2
+#define ERROR -1
+#define OK 0
+#define FATALERR 1
+#define NOACCT 2
+#define FILTERERR 3
+#define ACCESS 4
+
+static dev_t fdev; /* device of file pointed to by symlink */
+static ino_t fino; /* inode of file pointed to by symlink */
+static FILE *cfp; /* control file */
+static pid_t of_pid; /* process id of output filter, if any */
+static int child; /* id of any filters */
+static int job_dfcnt; /* count of datafiles in current user job */
+static int lfd; /* lock file descriptor */
+static int ofd; /* output filter file descriptor */
+static int tfd = -1; /* output filter temp file output */
+static int pfd; /* prstatic inter file descriptor */
+static int prchild; /* id of pr process */
+static char title[80]; /* ``pr'' title */
+static char locale[80]; /* ``pr'' locale */
+
+/* these two are set from pp->daemon_user, but only if they are needed */
+static char *daemon_uname; /* set from pwd->pw_name */
+static int daemon_defgid;
+
+static char class[32]; /* classification field */
+static char origin_host[MAXHOSTNAMELEN]; /* user's host machine */
+ /* indentation size in static characters */
+static char indent[10] = "-i0";
+static char jobname[100]; /* job or file name */
+static char length[10] = "-l"; /* page length in lines */
+static char logname[32]; /* user's login name */
+static char pxlength[10] = "-y"; /* page length in pixels */
+static char pxwidth[10] = "-x"; /* page width in pixels */
+/* tempstderr is the filename used to catch stderr from exec-ing filters */
+static char tempstderr[] = "errs.XXXXXXX";
+static char width[10] = "-w"; /* page width in static characters */
+#define TFILENAME "fltXXXXXX"
+static char tfile[] = TFILENAME; /* file name for filter output */
+
+static void abortpr(int _signo);
+static void alarmhandler(int _signo);
+static void banner(struct printer *_pp, char *_name1, char *_name2);
+static int dofork(const struct printer *_pp, int _action);
+static int dropit(int _c);
+static int execfilter(struct printer *_pp, char *_f_cmd, char **_f_av,
+ int _infd, int _outfd);
+static void init(struct printer *_pp);
+static void openpr(const struct printer *_pp);
+static void opennet(const struct printer *_pp);
+static void opentty(const struct printer *_pp);
+static void openrem(const struct printer *pp);
+static int print(struct printer *_pp, int _format, char *_file);
+static int printit(struct printer *_pp, char *_file);
+static void pstatus(const struct printer *_pp, const char *_msg, ...)
+ __printflike(2, 3);
+static char response(const struct printer *_pp);
+static void scan_out(struct printer *_pp, int _scfd, char *_scsp,
+ int _dlm);
+static char *scnline(int _key, char *_p, int _c);
+static int sendfile(struct printer *_pp, int _type, char *_file,
+ char _format, int _copyreq);
+static int sendit(struct printer *_pp, char *_file);
+static void sendmail(struct printer *_pp, char *_userid, int _bombed);
+static void setty(const struct printer *_pp);
+static void wait4data(struct printer *_pp, const char *_dfile);
+
+void
+printjob(struct printer *pp)
+{
+ struct stat stb;
+ register struct jobqueue *q, **qp;
+ struct jobqueue **queue;
+ register int i, nitems;
+ off_t pidoff;
+ pid_t printpid;
+ int errcnt, jobcount, statok, tempfd;
+
+ jobcount = 0;
+ init(pp); /* set up capabilities */
+ (void) write(STDOUT_FILENO, "", 1); /* ack that daemon is started */
+ (void) close(STDERR_FILENO); /* set up log file */
+ if (open(pp->log_file, O_WRONLY|O_APPEND, LOG_FILE_MODE) < 0) {
+ syslog(LOG_ERR, "%s: open(%s): %m", pp->printer,
+ pp->log_file);
+ (void) open(_PATH_DEVNULL, O_WRONLY);
+ }
+ if(setgid(getegid()) != 0) err(1, "setgid() failed");
+ printpid = getpid(); /* for use with lprm */
+ setpgid((pid_t)0, printpid);
+
+ /*
+ * At initial lpd startup, printjob may be called with various
+ * signal handlers in effect. After that initial startup, any
+ * calls to printjob will have a *different* set of signal-handlers
+ * in effect. Make sure all handlers are the ones we want.
+ */
+ signal(SIGCHLD, SIG_DFL);
+ signal(SIGHUP, abortpr);
+ signal(SIGINT, abortpr);
+ signal(SIGQUIT, abortpr);
+ signal(SIGTERM, abortpr);
+
+ /*
+ * uses short form file names
+ */
+ if (chdir(pp->spool_dir) < 0) {
+ syslog(LOG_ERR, "%s: chdir(%s): %m", pp->printer,
+ pp->spool_dir);
+ exit(1);
+ }
+ statok = stat(pp->lock_file, &stb);
+ if (statok == 0 && (stb.st_mode & LFM_PRINT_DIS))
+ exit(0); /* printing disabled */
+ umask(S_IWOTH);
+ lfd = open(pp->lock_file, O_WRONLY|O_CREAT|O_EXLOCK|O_NONBLOCK,
+ LOCK_FILE_MODE);
+ if (lfd < 0) {
+ if (errno == EWOULDBLOCK) /* active daemon present */
+ exit(0);
+ syslog(LOG_ERR, "%s: open(%s): %m", pp->printer,
+ pp->lock_file);
+ exit(1);
+ }
+ /*
+ * If the initial call to stat() failed, then lock_file will have
+ * been created by open(). Update &stb to match that new file.
+ */
+ if (statok != 0)
+ statok = stat(pp->lock_file, &stb);
+ /* turn off non-blocking mode (was turned on for lock effects only) */
+ if (fcntl(lfd, F_SETFL, 0) < 0) {
+ syslog(LOG_ERR, "%s: fcntl(%s): %m", pp->printer,
+ pp->lock_file);
+ exit(1);
+ }
+ ftruncate(lfd, 0);
+ /*
+ * write process id for others to know
+ */
+ sprintf(line, "%u\n", printpid);
+ pidoff = i = strlen(line);
+ if (write(lfd, line, i) != i) {
+ syslog(LOG_ERR, "%s: write(%s): %m", pp->printer,
+ pp->lock_file);
+ exit(1);
+ }
+ /*
+ * search the spool directory for work and sort by queue order.
+ */
+ if ((nitems = getq(pp, &queue)) < 0) {
+ syslog(LOG_ERR, "%s: can't scan %s", pp->printer,
+ pp->spool_dir);
+ exit(1);
+ }
+ if (nitems == 0) /* no work to do */
+ exit(0);
+ if (stb.st_mode & LFM_RESET_QUE) { /* reset queue flag */
+ if (fchmod(lfd, stb.st_mode & ~LFM_RESET_QUE) < 0)
+ syslog(LOG_ERR, "%s: fchmod(%s): %m", pp->printer,
+ pp->lock_file);
+ }
+
+ /* create a file which will be used to hold stderr from filters */
+ if ((tempfd = mkstemp(tempstderr)) == -1) {
+ syslog(LOG_ERR, "%s: mkstemp(%s): %m", pp->printer,
+ tempstderr);
+ exit(1);
+ }
+ if ((i = fchmod(tempfd, 0664)) == -1) {
+ syslog(LOG_ERR, "%s: fchmod(%s): %m", pp->printer,
+ tempstderr);
+ exit(1);
+ }
+ /* lpd doesn't need it to be open, it just needs it to exist */
+ close(tempfd);
+
+ openpr(pp); /* open printer or remote */
+again:
+ /*
+ * we found something to do now do it --
+ * write the name of the current control file into the lock file
+ * so the spool queue program can tell what we're working on
+ */
+ for (qp = queue; nitems--; free((char *) q)) {
+ q = *qp++;
+ if (stat(q->job_cfname, &stb) < 0)
+ continue;
+ errcnt = 0;
+ restart:
+ (void) lseek(lfd, pidoff, 0);
+ (void) snprintf(line, sizeof(line), "%s\n", q->job_cfname);
+ i = strlen(line);
+ if (write(lfd, line, i) != i)
+ syslog(LOG_ERR, "%s: write(%s): %m", pp->printer,
+ pp->lock_file);
+ if (!pp->remote)
+ i = printit(pp, q->job_cfname);
+ else
+ i = sendit(pp, q->job_cfname);
+ /*
+ * Check to see if we are supposed to stop printing or
+ * if we are to rebuild the queue.
+ */
+ if (fstat(lfd, &stb) == 0) {
+ /* stop printing before starting next job? */
+ if (stb.st_mode & LFM_PRINT_DIS)
+ goto done;
+ /* rebuild queue (after lpc topq) */
+ if (stb.st_mode & LFM_RESET_QUE) {
+ for (free(q); nitems--; free(q))
+ q = *qp++;
+ if (fchmod(lfd, stb.st_mode & ~LFM_RESET_QUE)
+ < 0)
+ syslog(LOG_WARNING,
+ "%s: fchmod(%s): %m",
+ pp->printer, pp->lock_file);
+ break;
+ }
+ }
+ if (i == OK) /* all files of this job printed */
+ jobcount++;
+ else if (i == REPRINT && ++errcnt < 5) {
+ /* try reprinting the job */
+ syslog(LOG_INFO, "restarting %s", pp->printer);
+ if (of_pid > 0) {
+ kill(of_pid, SIGCONT); /* to be sure */
+ (void) close(ofd);
+ while ((i = wait(NULL)) > 0 && i != of_pid)
+ ;
+ if (i < 0)
+ syslog(LOG_WARNING, "%s: after kill(of=%d), wait() returned: %m",
+ pp->printer, of_pid);
+ of_pid = 0;
+ }
+ (void) close(pfd); /* close printer */
+ if (ftruncate(lfd, pidoff) < 0)
+ syslog(LOG_WARNING, "%s: ftruncate(%s): %m",
+ pp->printer, pp->lock_file);
+ openpr(pp); /* try to reopen printer */
+ goto restart;
+ } else {
+ syslog(LOG_WARNING, "%s: job could not be %s (%s)",
+ pp->printer,
+ pp->remote ? "sent to remote host" : "printed",
+ q->job_cfname);
+ if (i == REPRINT) {
+ /* ensure we don't attempt this job again */
+ (void) unlink(q->job_cfname);
+ q->job_cfname[0] = 'd';
+ (void) unlink(q->job_cfname);
+ if (logname[0])
+ sendmail(pp, logname, FATALERR);
+ }
+ }
+ }
+ free(queue);
+ /*
+ * search the spool directory for more work.
+ */
+ if ((nitems = getq(pp, &queue)) < 0) {
+ syslog(LOG_ERR, "%s: can't scan %s", pp->printer,
+ pp->spool_dir);
+ exit(1);
+ }
+ if (nitems == 0) { /* no more work to do */
+ done:
+ if (jobcount > 0) { /* jobs actually printed */
+ if (!pp->no_formfeed && !pp->tof)
+ (void) write(ofd, pp->form_feed,
+ strlen(pp->form_feed));
+ if (pp->trailer != NULL) /* output trailer */
+ (void) write(ofd, pp->trailer,
+ strlen(pp->trailer));
+ }
+ (void) close(ofd);
+ (void) wait(NULL);
+ (void) unlink(tempstderr);
+ exit(0);
+ }
+ goto again;
+}
+
+char fonts[4][50]; /* fonts for troff */
+
+char ifonts[4][40] = {
+ _PATH_VFONTR,
+ _PATH_VFONTI,
+ _PATH_VFONTB,
+ _PATH_VFONTS,
+};
+
+/*
+ * The remaining part is the reading of the control file (cf)
+ * and performing the various actions.
+ */
+static int
+printit(struct printer *pp, char *file)
+{
+ register int i;
+ char *cp;
+ int bombed, didignorehdr;
+
+ bombed = OK;
+ didignorehdr = 0;
+ /*
+ * open control file; ignore if no longer there.
+ */
+ if ((cfp = fopen(file, "r")) == NULL) {
+ syslog(LOG_INFO, "%s: fopen(%s): %m", pp->printer, file);
+ return (OK);
+ }
+ /*
+ * Reset troff fonts.
+ */
+ for (i = 0; i < 4; i++)
+ strcpy(fonts[i], ifonts[i]);
+ sprintf(&width[2], "%ld", pp->page_width);
+ strcpy(indent+2, "0");
+
+ /* initialize job-specific count of datafiles processed */
+ job_dfcnt = 0;
+
+ /*
+ * read the control file for work to do
+ *
+ * file format -- first character in the line is a command
+ * rest of the line is the argument.
+ * valid commands are:
+ *
+ * S -- "stat info" for symbolic link protection
+ * J -- "job name" on banner page
+ * C -- "class name" on banner page
+ * L -- "literal" user's name to print on banner
+ * T -- "title" for pr
+ * H -- "host name" of machine where lpr was done
+ * P -- "person" user's login name
+ * I -- "indent" amount to indent output
+ * R -- laser dpi "resolution"
+ * f -- "file name" name of text file to print
+ * l -- "file name" text file with control chars
+ * o -- "file name" postscript file, according to
+ * the RFC. Here it is treated like an 'f'.
+ * p -- "file name" text file to print with pr(1)
+ * t -- "file name" troff(1) file to print
+ * n -- "file name" ditroff(1) file to print
+ * d -- "file name" dvi file to print
+ * g -- "file name" plot(1G) file to print
+ * v -- "file name" plain raster file to print
+ * c -- "file name" cifplot file to print
+ * 1 -- "R font file" for troff
+ * 2 -- "I font file" for troff
+ * 3 -- "B font file" for troff
+ * 4 -- "S font file" for troff
+ * N -- "name" of file (used by lpq)
+ * U -- "unlink" name of file to remove
+ * (after we print it. (Pass 2 only)).
+ * M -- "mail" to user when done printing
+ * Z -- "locale" for pr
+ *
+ * get_line reads a line and expands tabs to blanks
+ */
+
+ /* pass 1 */
+
+ while (get_line(cfp))
+ switch (line[0]) {
+ case 'H':
+ strlcpy(origin_host, line + 1, sizeof(origin_host));
+ if (class[0] == '\0') {
+ strlcpy(class, line+1, sizeof(class));
+ }
+ continue;
+
+ case 'P':
+ strlcpy(logname, line + 1, sizeof(logname));
+ if (pp->restricted) { /* restricted */
+ if (getpwnam(logname) == NULL) {
+ bombed = NOACCT;
+ sendmail(pp, line+1, bombed);
+ goto pass2;
+ }
+ }
+ continue;
+
+ case 'S':
+ cp = line+1;
+ i = 0;
+ while (*cp >= '0' && *cp <= '9')
+ i = i * 10 + (*cp++ - '0');
+ fdev = i;
+ cp++;
+ i = 0;
+ while (*cp >= '0' && *cp <= '9')
+ i = i * 10 + (*cp++ - '0');
+ fino = i;
+ continue;
+
+ case 'J':
+ if (line[1] != '\0') {
+ strlcpy(jobname, line + 1, sizeof(jobname));
+ } else
+ strcpy(jobname, " ");
+ continue;
+
+ case 'C':
+ if (line[1] != '\0')
+ strlcpy(class, line + 1, sizeof(class));
+ else if (class[0] == '\0') {
+ /* XXX - why call gethostname instead of
+ * just strlcpy'ing local_host? */
+ gethostname(class, sizeof(class));
+ class[sizeof(class) - 1] = '\0';
+ }
+ continue;
+
+ case 'T': /* header title for pr */
+ strlcpy(title, line + 1, sizeof(title));
+ continue;
+
+ case 'L': /* identification line */
+ if (!pp->no_header && !pp->header_last)
+ banner(pp, line+1, jobname);
+ continue;
+
+ case '1': /* troff fonts */
+ case '2':
+ case '3':
+ case '4':
+ if (line[1] != '\0') {
+ strlcpy(fonts[line[0]-'1'], line + 1,
+ (size_t)50);
+ }
+ continue;
+
+ case 'W': /* page width */
+ strlcpy(width+2, line + 1, sizeof(width) - 2);
+ continue;
+
+ case 'I': /* indent amount */
+ strlcpy(indent+2, line + 1, sizeof(indent) - 2);
+ continue;
+
+ case 'Z': /* locale for pr */
+ strlcpy(locale, line + 1, sizeof(locale));
+ continue;
+
+ default: /* some file to print */
+ /* only lowercase cmd-codes include a file-to-print */
+ if ((line[0] < 'a') || (line[0] > 'z')) {
+ /* ignore any other lines */
+ if (lflag <= 1)
+ continue;
+ if (!didignorehdr) {
+ syslog(LOG_INFO, "%s: in %s :",
+ pp->printer, file);
+ didignorehdr = 1;
+ }
+ syslog(LOG_INFO, "%s: ignoring line: '%c' %s",
+ pp->printer, line[0], &line[1]);
+ continue;
+ }
+ i = print(pp, line[0], line+1);
+ switch (i) {
+ case ERROR:
+ if (bombed == OK)
+ bombed = FATALERR;
+ break;
+ case REPRINT:
+ (void) fclose(cfp);
+ return (REPRINT);
+ case FILTERERR:
+ case ACCESS:
+ bombed = i;
+ sendmail(pp, logname, bombed);
+ }
+ title[0] = '\0';
+ continue;
+
+ case 'N':
+ case 'U':
+ case 'M':
+ case 'R':
+ continue;
+ }
+
+ /* pass 2 */
+
+pass2:
+ fseek(cfp, 0L, 0);
+ while (get_line(cfp))
+ switch (line[0]) {
+ case 'L': /* identification line */
+ if (!pp->no_header && pp->header_last)
+ banner(pp, line+1, jobname);
+ continue;
+
+ case 'M':
+ if (bombed < NOACCT) /* already sent if >= NOACCT */
+ sendmail(pp, line+1, bombed);
+ continue;
+
+ case 'U':
+ if (strchr(line+1, '/'))
+ continue;
+ (void) unlink(line+1);
+ }
+ /*
+ * clean-up in case another control file exists
+ */
+ (void) fclose(cfp);
+ (void) unlink(file);
+ return (bombed == OK ? OK : ERROR);
+}
+
+/*
+ * Print a file.
+ * Set up the chain [ PR [ | {IF, OF} ] ] or {IF, RF, TF, NF, DF, CF, VF}.
+ * Return -1 if a non-recoverable error occurred,
+ * 2 if the filter detected some errors (but printed the job anyway),
+ * 1 if we should try to reprint this job and
+ * 0 if all is well.
+ * Note: all filters take stdin as the file, stdout as the printer,
+ * stderr as the log file, and must not ignore SIGINT.
+ */
+static int
+print(struct printer *pp, int format, char *file)
+{
+ register int n, i;
+ register char *prog;
+ int fi, fo;
+ FILE *fp;
+ char *av[15], buf[SPL_BUFSIZ];
+ pid_t wpid;
+ int p[2], retcode, stopped, wstatus, wstatus_set;
+ struct stat stb;
+
+ /* Make sure the entire data file has arrived. */
+ wait4data(pp, file);
+
+ if (lstat(file, &stb) < 0 || (fi = open(file, O_RDONLY)) < 0) {
+ syslog(LOG_INFO, "%s: unable to open %s ('%c' line)",
+ pp->printer, file, format);
+ return (ERROR);
+ }
+ /*
+ * Check to see if data file is a symbolic link. If so, it should
+ * still point to the same file or someone is trying to print
+ * something he shouldn't.
+ */
+ if ((stb.st_mode & S_IFMT) == S_IFLNK && fstat(fi, &stb) == 0 &&
+ (stb.st_dev != fdev || stb.st_ino != fino))
+ return (ACCESS);
+
+ job_dfcnt++; /* increment datafile counter for this job */
+ stopped = 0; /* output filter is not stopped */
+
+ /* everything seems OK, start it up */
+ if (!pp->no_formfeed && !pp->tof) { /* start on a fresh page */
+ (void) write(ofd, pp->form_feed, strlen(pp->form_feed));
+ pp->tof = 1;
+ }
+ if (pp->filters[LPF_INPUT] == NULL
+ && (format == 'f' || format == 'l' || format == 'o')) {
+ pp->tof = 0;
+ while ((n = read(fi, buf, SPL_BUFSIZ)) > 0)
+ if (write(ofd, buf, n) != n) {
+ (void) close(fi);
+ return (REPRINT);
+ }
+ (void) close(fi);
+ return (OK);
+ }
+ switch (format) {
+ case 'p': /* print file using 'pr' */
+ if (pp->filters[LPF_INPUT] == NULL) { /* use output filter */
+ prog = _PATH_PR;
+ i = 0;
+ av[i++] = "pr";
+ av[i++] = width;
+ av[i++] = length;
+ av[i++] = "-h";
+ av[i++] = *title ? title : " ";
+ av[i++] = "-L";
+ av[i++] = *locale ? locale : "C";
+ av[i++] = "-F";
+ av[i] = NULL;
+ fo = ofd;
+ goto start;
+ }
+ pipe(p);
+ if ((prchild = dofork(pp, DORETURN)) == 0) { /* child */
+ dup2(fi, STDIN_FILENO); /* file is stdin */
+ dup2(p[1], STDOUT_FILENO); /* pipe is stdout */
+ closelog();
+ closeallfds(3);
+ execl(_PATH_PR, "pr", width, length,
+ "-h", *title ? title : " ",
+ "-L", *locale ? locale : "C",
+ "-F", (char *)0);
+ syslog(LOG_ERR, "cannot execl %s", _PATH_PR);
+ exit(2);
+ }
+ (void) close(p[1]); /* close output side */
+ (void) close(fi);
+ if (prchild < 0) {
+ prchild = 0;
+ (void) close(p[0]);
+ return (ERROR);
+ }
+ fi = p[0]; /* use pipe for input */
+ case 'f': /* print plain text file */
+ prog = pp->filters[LPF_INPUT];
+ av[1] = width;
+ av[2] = length;
+ av[3] = indent;
+ n = 4;
+ break;
+ case 'o': /* print postscript file */
+ /*
+ * Treat this as a "plain file with control characters", and
+ * assume the standard LPF_INPUT filter will recognize that
+ * the data is postscript and know what to do with it. These
+ * 'o'-file requests could come from MacOS 10.1 systems.
+ * (later versions of MacOS 10 will explicitly use 'l')
+ * A postscript file can contain binary data, which is why 'l'
+ * is somewhat more appropriate than 'f'.
+ */
+ /* FALLTHROUGH */
+ case 'l': /* like 'f' but pass control characters */
+ prog = pp->filters[LPF_INPUT];
+ av[1] = "-c";
+ av[2] = width;
+ av[3] = length;
+ av[4] = indent;
+ n = 5;
+ break;
+ case 'r': /* print a fortran text file */
+ prog = pp->filters[LPF_FORTRAN];
+ av[1] = width;
+ av[2] = length;
+ n = 3;
+ break;
+ case 't': /* print troff output */
+ case 'n': /* print ditroff output */
+ case 'd': /* print tex output */
+ (void) unlink(".railmag");
+ if ((fo = creat(".railmag", FILMOD)) < 0) {
+ syslog(LOG_ERR, "%s: cannot create .railmag",
+ pp->printer);
+ (void) unlink(".railmag");
+ } else {
+ for (n = 0; n < 4; n++) {
+ if (fonts[n][0] != '/')
+ (void) write(fo, _PATH_VFONT,
+ sizeof(_PATH_VFONT) - 1);
+ (void) write(fo, fonts[n], strlen(fonts[n]));
+ (void) write(fo, "\n", 1);
+ }
+ (void) close(fo);
+ }
+ prog = (format == 't') ? pp->filters[LPF_TROFF]
+ : ((format == 'n') ? pp->filters[LPF_DITROFF]
+ : pp->filters[LPF_DVI]);
+ av[1] = pxwidth;
+ av[2] = pxlength;
+ n = 3;
+ break;
+ case 'c': /* print cifplot output */
+ prog = pp->filters[LPF_CIFPLOT];
+ av[1] = pxwidth;
+ av[2] = pxlength;
+ n = 3;
+ break;
+ case 'g': /* print plot(1G) output */
+ prog = pp->filters[LPF_GRAPH];
+ av[1] = pxwidth;
+ av[2] = pxlength;
+ n = 3;
+ break;
+ case 'v': /* print raster output */
+ prog = pp->filters[LPF_RASTER];
+ av[1] = pxwidth;
+ av[2] = pxlength;
+ n = 3;
+ break;
+ default:
+ (void) close(fi);
+ syslog(LOG_ERR, "%s: illegal format character '%c'",
+ pp->printer, format);
+ return (ERROR);
+ }
+ if (prog == NULL) {
+ (void) close(fi);
+ syslog(LOG_ERR,
+ "%s: no filter found in printcap for format character '%c'",
+ pp->printer, format);
+ return (ERROR);
+ }
+ if ((av[0] = strrchr(prog, '/')) != NULL)
+ av[0]++;
+ else
+ av[0] = prog;
+ av[n++] = "-n";
+ av[n++] = logname;
+ av[n++] = "-h";
+ av[n++] = origin_host;
+ av[n++] = pp->acct_file;
+ av[n] = NULL;
+ fo = pfd;
+ if (of_pid > 0) { /* stop output filter */
+ write(ofd, "\031\1", 2);
+ while ((wpid =
+ wait3(&wstatus, WUNTRACED, 0)) > 0 && wpid != of_pid)
+ ;
+ if (wpid < 0)
+ syslog(LOG_WARNING,
+ "%s: after stopping 'of', wait3() returned: %m",
+ pp->printer);
+ else if (!WIFSTOPPED(wstatus)) {
+ (void) close(fi);
+ syslog(LOG_WARNING, "%s: output filter died "
+ "(pid=%d retcode=%d termsig=%d)",
+ pp->printer, of_pid, WEXITSTATUS(wstatus),
+ WTERMSIG(wstatus));
+ return (REPRINT);
+ }
+ stopped++;
+ }
+start:
+ if ((child = dofork(pp, DORETURN)) == 0) { /* child */
+ dup2(fi, STDIN_FILENO);
+ dup2(fo, STDOUT_FILENO);
+ /* setup stderr for the filter (child process)
+ * so it goes to our temporary errors file */
+ n = open(tempstderr, O_WRONLY|O_TRUNC, 0664);
+ if (n >= 0)
+ dup2(n, STDERR_FILENO);
+ closelog();
+ closeallfds(3);
+ execv(prog, av);
+ syslog(LOG_ERR, "%s: cannot execv(%s): %m", pp->printer,
+ prog);
+ exit(2);
+ }
+ (void) close(fi);
+ wstatus_set = 0;
+ if (child < 0)
+ retcode = 100;
+ else {
+ while ((wpid = wait(&wstatus)) > 0 && wpid != child)
+ ;
+ if (wpid < 0) {
+ retcode = 100;
+ syslog(LOG_WARNING,
+ "%s: after execv(%s), wait() returned: %m",
+ pp->printer, prog);
+ } else {
+ wstatus_set = 1;
+ retcode = WEXITSTATUS(wstatus);
+ }
+ }
+ child = 0;
+ prchild = 0;
+ if (stopped) { /* restart output filter */
+ if (kill(of_pid, SIGCONT) < 0) {
+ syslog(LOG_ERR, "cannot restart output filter");
+ exit(1);
+ }
+ }
+ pp->tof = 0;
+
+ /* Copy the filter's output to "lf" logfile */
+ if ((fp = fopen(tempstderr, "r"))) {
+ while (fgets(buf, sizeof(buf), fp))
+ fputs(buf, stderr);
+ fclose(fp);
+ }
+
+ if (wstatus_set && !WIFEXITED(wstatus)) {
+ syslog(LOG_WARNING, "%s: filter '%c' terminated (termsig=%d)",
+ pp->printer, format, WTERMSIG(wstatus));
+ return (ERROR);
+ }
+ switch (retcode) {
+ case 0:
+ pp->tof = 1;
+ return (OK);
+ case 1:
+ return (REPRINT);
+ case 2:
+ return (ERROR);
+ default:
+ syslog(LOG_WARNING, "%s: filter '%c' exited (retcode=%d)",
+ pp->printer, format, retcode);
+ return (FILTERERR);
+ }
+}
+
+/*
+ * Send the daemon control file (cf) and any data files.
+ * Return -1 if a non-recoverable error occurred, 1 if a recoverable error and
+ * 0 if all is well.
+ */
+static int
+sendit(struct printer *pp, char *file)
+{
+ int dfcopies, err, i;
+ char *cp, last[sizeof(line)];
+
+ /*
+ * open control file
+ */
+ if ((cfp = fopen(file, "r")) == NULL)
+ return (OK);
+
+ /* initialize job-specific count of datafiles processed */
+ job_dfcnt = 0;
+
+ /*
+ * read the control file for work to do
+ *
+ * file format -- first character in the line is a command
+ * rest of the line is the argument.
+ * commands of interest are:
+ *
+ * a-z -- "file name" name of file to print
+ * U -- "unlink" name of file to remove
+ * (after we print it. (Pass 2 only)).
+ */
+
+ /*
+ * pass 1
+ */
+ err = OK;
+ while (get_line(cfp)) {
+ again:
+ if (line[0] == 'S') {
+ cp = line+1;
+ i = 0;
+ while (*cp >= '0' && *cp <= '9')
+ i = i * 10 + (*cp++ - '0');
+ fdev = i;
+ cp++;
+ i = 0;
+ while (*cp >= '0' && *cp <= '9')
+ i = i * 10 + (*cp++ - '0');
+ fino = i;
+ } else if (line[0] == 'H') {
+ strlcpy(origin_host, line + 1, sizeof(origin_host));
+ if (class[0] == '\0') {
+ strlcpy(class, line + 1, sizeof(class));
+ }
+ } else if (line[0] == 'P') {
+ strlcpy(logname, line + 1, sizeof(logname));
+ if (pp->restricted) { /* restricted */
+ if (getpwnam(logname) == NULL) {
+ sendmail(pp, line+1, NOACCT);
+ err = ERROR;
+ break;
+ }
+ }
+ } else if (line[0] == 'I') {
+ strlcpy(indent+2, line + 1, sizeof(indent) - 2);
+ } else if (line[0] >= 'a' && line[0] <= 'z') {
+ dfcopies = 1;
+ strcpy(last, line);
+ while ((i = get_line(cfp)) != 0) {
+ if (strcmp(last, line) != 0)
+ break;
+ dfcopies++;
+ }
+ switch (sendfile(pp, '\3', last+1, *last, dfcopies)) {
+ case OK:
+ if (i)
+ goto again;
+ break;
+ case REPRINT:
+ (void) fclose(cfp);
+ return (REPRINT);
+ case ACCESS:
+ sendmail(pp, logname, ACCESS);
+ case ERROR:
+ err = ERROR;
+ }
+ break;
+ }
+ }
+ if (err == OK && sendfile(pp, '\2', file, '\0', 1) > 0) {
+ (void) fclose(cfp);
+ return (REPRINT);
+ }
+ /*
+ * pass 2
+ */
+ fseek(cfp, 0L, 0);
+ while (get_line(cfp))
+ if (line[0] == 'U' && !strchr(line+1, '/'))
+ (void) unlink(line+1);
+ /*
+ * clean-up in case another control file exists
+ */
+ (void) fclose(cfp);
+ (void) unlink(file);
+ return (err);
+}
+
+/*
+ * Send a data file to the remote machine and spool it.
+ * Return positive if we should try resending.
+ */
+static int
+sendfile(struct printer *pp, int type, char *file, char format, int copyreq)
+{
+ int i, amt;
+ struct stat stb;
+ char *av[15], *filtcmd;
+ char buf[SPL_BUFSIZ], opt_c[4], opt_h[4], opt_n[4];
+ int copycnt, filtstat, narg, resp, sfd, sfres, sizerr, statrc;
+
+ /* Make sure the entire data file has arrived. */
+ wait4data(pp, file);
+
+ statrc = lstat(file, &stb);
+ if (statrc < 0) {
+ syslog(LOG_ERR, "%s: error from lstat(%s): %m",
+ pp->printer, file);
+ return (ERROR);
+ }
+ sfd = open(file, O_RDONLY);
+ if (sfd < 0) {
+ syslog(LOG_ERR, "%s: error from open(%s,O_RDONLY): %m",
+ pp->printer, file);
+ return (ERROR);
+ }
+ /*
+ * Check to see if data file is a symbolic link. If so, it should
+ * still point to the same file or someone is trying to print something
+ * he shouldn't.
+ */
+ if ((stb.st_mode & S_IFMT) == S_IFLNK && fstat(sfd, &stb) == 0 &&
+ (stb.st_dev != fdev || stb.st_ino != fino)) {
+ close(sfd);
+ return (ACCESS);
+ }
+
+ /* Everything seems OK for reading the file, now to send it */
+ filtcmd = NULL;
+ sizerr = 0;
+ tfd = -1;
+ if (type == '\3') {
+ /*
+ * Type == 3 means this is a datafile, not a control file.
+ * Increment the counter of data-files in this job, and
+ * then check for input or output filters (which are only
+ * applied to datafiles, not control files).
+ */
+ job_dfcnt++;
+
+ /*
+ * Note that here we are filtering datafiles, one at a time,
+ * as they are sent to the remote machine. Here, the *only*
+ * difference between an input filter (`if=') and an output
+ * filter (`of=') is the argument list that the filter is
+ * started up with. Here, the output filter is executed
+ * for each individual file as it is sent. This is not the
+ * same as local print queues, where the output filter is
+ * started up once, and then all jobs are passed thru that
+ * single invocation of the output filter.
+ *
+ * Also note that a queue for a remote-machine can have an
+ * input filter or an output filter, but not both.
+ */
+ if (pp->filters[LPF_INPUT]) {
+ filtcmd = pp->filters[LPF_INPUT];
+ av[0] = filtcmd;
+ narg = 0;
+ strcpy(opt_c, "-c");
+ strcpy(opt_h, "-h");
+ strcpy(opt_n, "-n");
+ if (format == 'l')
+ av[++narg] = opt_c;
+ av[++narg] = width;
+ av[++narg] = length;
+ av[++narg] = indent;
+ av[++narg] = opt_n;
+ av[++narg] = logname;
+ av[++narg] = opt_h;
+ av[++narg] = origin_host;
+ av[++narg] = pp->acct_file;
+ av[++narg] = NULL;
+ } else if (pp->filters[LPF_OUTPUT]) {
+ filtcmd = pp->filters[LPF_OUTPUT];
+ av[0] = filtcmd;
+ narg = 0;
+ av[++narg] = width;
+ av[++narg] = length;
+ av[++narg] = NULL;
+ }
+ }
+ if (filtcmd) {
+ /*
+ * If there is an input or output filter, we have to run
+ * the datafile thru that filter and store the result as
+ * a temporary spool file, because the protocol requires
+ * that we send the remote host the file-size before we
+ * start to send any of the data.
+ */
+ strcpy(tfile, TFILENAME);
+ tfd = mkstemp(tfile);
+ if (tfd == -1) {
+ syslog(LOG_ERR, "%s: mkstemp(%s): %m", pp->printer,
+ TFILENAME);
+ sfres = ERROR;
+ goto return_sfres;
+ }
+ filtstat = execfilter(pp, filtcmd, av, sfd, tfd);
+
+ /* process the return-code from the filter */
+ switch (filtstat) {
+ case 0:
+ break;
+ case 1:
+ sfres = REPRINT;
+ goto return_sfres;
+ case 2:
+ sfres = ERROR;
+ goto return_sfres;
+ default:
+ syslog(LOG_WARNING,
+ "%s: filter '%c' exited (retcode=%d)",
+ pp->printer, format, filtstat);
+ sfres = FILTERERR;
+ goto return_sfres;
+ }
+ statrc = fstat(tfd, &stb); /* to find size of tfile */
+ if (statrc < 0) {
+ syslog(LOG_ERR,
+ "%s: error processing 'if', fstat(%s): %m",
+ pp->printer, tfile);
+ sfres = ERROR;
+ goto return_sfres;
+ }
+ close(sfd);
+ sfd = tfd;
+ lseek(sfd, 0, SEEK_SET);
+ }
+
+ copycnt = 0;
+sendagain:
+ copycnt++;
+
+ if (copycnt < 2)
+ (void) sprintf(buf, "%c%" PRId64 " %s\n", type, stb.st_size,
+ file);
+ else
+ (void) sprintf(buf, "%c%" PRId64 " %s_c%d\n", type, stb.st_size,
+ file, copycnt);
+ amt = strlen(buf);
+ for (i = 0; ; i++) {
+ if (write(pfd, buf, amt) != amt ||
+ (resp = response(pp)) < 0 || resp == '\1') {
+ sfres = REPRINT;
+ goto return_sfres;
+ } else if (resp == '\0')
+ break;
+ if (i == 0)
+ pstatus(pp,
+ "no space on remote; waiting for queue to drain");
+ if (i == 10)
+ syslog(LOG_ALERT, "%s: can't send to %s; queue full",
+ pp->printer, pp->remote_host);
+ sleep(5 * 60);
+ }
+ if (i)
+ pstatus(pp, "sending to %s", pp->remote_host);
+ /*
+ * XXX - we should change trstat_init()/trstat_write() to include
+ * the copycnt in the statistics record it may write.
+ */
+ if (type == '\3')
+ trstat_init(pp, file, job_dfcnt);
+ for (i = 0; i < stb.st_size; i += SPL_BUFSIZ) {
+ amt = SPL_BUFSIZ;
+ if (i + amt > stb.st_size)
+ amt = stb.st_size - i;
+ if (sizerr == 0 && read(sfd, buf, amt) != amt)
+ sizerr = 1;
+ if (write(pfd, buf, amt) != amt) {
+ sfres = REPRINT;
+ goto return_sfres;
+ }
+ }
+
+ if (sizerr) {
+ syslog(LOG_INFO, "%s: %s: changed size", pp->printer, file);
+ /* tell recvjob to ignore this file */
+ (void) write(pfd, "\1", 1);
+ sfres = ERROR;
+ goto return_sfres;
+ }
+ if (write(pfd, "", 1) != 1 || response(pp)) {
+ sfres = REPRINT;
+ goto return_sfres;
+ }
+ if (type == '\3') {
+ trstat_write(pp, TR_SENDING, stb.st_size, logname,
+ pp->remote_host, origin_host);
+ /*
+ * Usually we only need to send one copy of a datafile,
+ * because the control-file will simply print the same
+ * file multiple times. However, some printers ignore
+ * the control file, and simply print each data file as
+ * it arrives. For such "remote hosts", we need to
+ * transfer the same data file multiple times. Such a
+ * a host is indicated by adding 'rc' to the printcap
+ * entry.
+ * XXX - Right now this ONLY works for remote hosts which
+ * do ignore the name of the data file, because
+ * this sends the file multiple times with slight
+ * changes to the filename. To do this right would
+ * require that we also rewrite the control file
+ * to match those filenames.
+ */
+ if (pp->resend_copies && (copycnt < copyreq)) {
+ lseek(sfd, 0, SEEK_SET);
+ goto sendagain;
+ }
+ }
+ sfres = OK;
+
+return_sfres:
+ (void)close(sfd);
+ if (tfd != -1) {
+ /*
+ * If tfd is set, then it is the same value as sfd, and
+ * therefore it is already closed at this point. All
+ * we need to do is remove the temporary file.
+ */
+ tfd = -1;
+ unlink(tfile);
+ }
+ return (sfres);
+}
+
+/*
+ * Some print servers send the control-file first, and then start sending the
+ * matching data file(s). That is not the correct order. If some queue is
+ * already printing an active job, then when that job is finished the queue
+ * may proceed to the control file of any incoming print job. This turns
+ * into a race between the process which is receiving the data file, and the
+ * process which is actively printing the very same file. When the remote
+ * server sends files in the wrong order, it is even possible that a queue
+ * will start to print a data file before the file has been created!
+ *
+ * So before we start to print() or send() a data file, we call this routine
+ * to make sure the data file is not still changing in size. Note that this
+ * problem will only happen for jobs arriving from a remote host, and that
+ * the process which has decided to print this job (and is thus making this
+ * check) is *not* the process which is receiving the job.
+ *
+ * A second benefit of this is that any incoming job is guaranteed to appear
+ * in a queue listing for at least a few seconds after it has arrived. Some
+ * lpr implementations get confused if they send a job and it disappears
+ * from the queue before they can check on it.
+ */
+#define MAXWAIT_ARRIVE 16 /* max to wait for the file to *exist* */
+#define MAXWAIT_4DATA (20*60) /* max to wait for it to stop changing */
+#define MINWAIT_4DATA 4 /* This value must be >= 1 */
+#define DEBUG_MINWAIT 1
+static void
+wait4data(struct printer *pp, const char *dfile)
+{
+ const char *cp;
+ int statres;
+ u_int sleepreq;
+ size_t dlen, hlen;
+ time_t amtslept, cur_time, prev_mtime;
+ struct stat statdf;
+
+ /* Skip these checks if the print job is from the local host. */
+ dlen = strlen(dfile);
+ hlen = strlen(local_host);
+ if (dlen > hlen) {
+ cp = dfile + dlen - hlen;
+ if (strcmp(cp, local_host) == 0)
+ return;
+ }
+
+ /*
+ * If this data file does not exist, then wait up to MAXWAIT_ARRIVE
+ * seconds for it to arrive.
+ */
+ amtslept = 0;
+ statres = stat(dfile, &statdf);
+ while (statres < 0 && amtslept < MAXWAIT_ARRIVE) {
+ if (amtslept == 0)
+ pstatus(pp, "Waiting for data file from remote host");
+ amtslept += MINWAIT_4DATA - sleep(MINWAIT_4DATA);
+ statres = stat(dfile, &statdf);
+ }
+ if (statres < 0) {
+ /* The file still does not exist, so just give up on it. */
+ syslog(LOG_WARNING, "%s: wait4data() abandoned wait for %s",
+ pp->printer, dfile);
+ return;
+ }
+
+ /*
+ * The file exists, so keep waiting until the data file has not
+ * changed for some reasonable amount of time. Extra care is
+ * taken when computing wait-times, just in case there are data
+ * files with a last-modify time in the future. While that is
+ * very unlikely to happen, it can happen when the system has
+ * a flakey time-of-day clock.
+ */
+ prev_mtime = statdf.st_mtime;
+ cur_time = time(NULL);
+ if (statdf.st_mtime >= cur_time - MINWAIT_4DATA) {
+ if (statdf.st_mtime >= cur_time) /* some TOD oddity */
+ sleepreq = MINWAIT_4DATA;
+ else
+ sleepreq = cur_time - statdf.st_mtime;
+ if (amtslept == 0)
+ pstatus(pp, "Waiting for data file from remote host");
+ amtslept += sleepreq - sleep(sleepreq);
+ statres = stat(dfile, &statdf);
+ }
+ sleepreq = MINWAIT_4DATA;
+ while (statres == 0 && amtslept < MAXWAIT_4DATA) {
+ if (statdf.st_mtime == prev_mtime)
+ break;
+ prev_mtime = statdf.st_mtime;
+ amtslept += sleepreq - sleep(sleepreq);
+ statres = stat(dfile, &statdf);
+ }
+
+ if (statres != 0)
+ syslog(LOG_WARNING, "%s: %s disappeared during wait4data()",
+ pp->printer, dfile);
+ else if (amtslept > MAXWAIT_4DATA)
+ syslog(LOG_WARNING,
+ "%s: %s still changing after %lu secs in wait4data()",
+ pp->printer, dfile, (unsigned long)amtslept);
+#if DEBUG_MINWAIT
+ else if (amtslept > MINWAIT_4DATA)
+ syslog(LOG_INFO, "%s: slept %lu secs in wait4data(%s)",
+ pp->printer, (unsigned long)amtslept, dfile);
+#endif
+}
+#undef MAXWAIT_ARRIVE
+#undef MAXWAIT_4DATA
+#undef MINWAIT_4DATA
+
+/*
+ * This routine is called to execute one of the filters as was
+ * specified in a printcap entry. While the child-process will read
+ * all of 'infd', it is up to the caller to close that file descriptor
+ * in the parent process.
+ */
+static int
+execfilter(struct printer *pp, char *f_cmd, char *f_av[], int infd, int outfd)
+{
+ pid_t fpid, wpid;
+ int errfd, retcode, wstatus;
+ FILE *errfp;
+ char buf[BUFSIZ], *slash;
+
+ fpid = dofork(pp, DORETURN);
+ if (fpid != 0) {
+ /*
+ * This is the parent process, which just waits for the child
+ * to complete and then returns the result. Note that it is
+ * the child process which reads the input stream.
+ */
+ if (fpid < 0)
+ retcode = 100;
+ else {
+ while ((wpid = wait(&wstatus)) > 0 &&
+ wpid != fpid)
+ ;
+ if (wpid < 0) {
+ retcode = 100;
+ syslog(LOG_WARNING,
+ "%s: after execv(%s), wait() returned: %m",
+ pp->printer, f_cmd);
+ } else
+ retcode = WEXITSTATUS(wstatus);
+ }
+
+ /*
+ * Copy everything the filter wrote to stderr from our
+ * temporary errors file to the "lf=" logfile.
+ */
+ errfp = fopen(tempstderr, "r");
+ if (errfp) {
+ while (fgets(buf, sizeof(buf), errfp))
+ fputs(buf, stderr);
+ fclose(errfp);
+ }
+
+ return (retcode);
+ }
+
+ /*
+ * This is the child process, which is the one that executes the
+ * given filter.
+ */
+ /*
+ * If the first parameter has any slashes in it, then change it
+ * to point to the first character after the last slash.
+ */
+ slash = strrchr(f_av[0], '/');
+ if (slash != NULL)
+ f_av[0] = slash + 1;
+ /*
+ * XXX - in the future, this should setup an explicit list of
+ * environment variables and use execve()!
+ */
+
+ /*
+ * Setup stdin, stdout, and stderr as we want them when the filter
+ * is running. Stderr is setup so it points to a temporary errors
+ * file, and the parent process will copy that temporary file to
+ * the real logfile after the filter completes.
+ */
+ dup2(infd, STDIN_FILENO);
+ dup2(outfd, STDOUT_FILENO);
+ errfd = open(tempstderr, O_WRONLY|O_TRUNC, 0664);
+ if (errfd >= 0)
+ dup2(errfd, STDERR_FILENO);
+ closelog();
+ closeallfds(3);
+ execv(f_cmd, f_av);
+ syslog(LOG_ERR, "%s: cannot execv(%s): %m", pp->printer, f_cmd);
+ exit(2);
+ /* NOTREACHED */
+}
+
+/*
+ * Check to make sure there have been no errors and that both programs
+ * are in sync with eachother.
+ * Return non-zero if the connection was lost.
+ */
+static char
+response(const struct printer *pp)
+{
+ char resp;
+
+ if (read(pfd, &resp, 1) != 1) {
+ syslog(LOG_INFO, "%s: lost connection", pp->printer);
+ return (-1);
+ }
+ return (resp);
+}
+
+/*
+ * Banner printing stuff
+ */
+static void
+banner(struct printer *pp, char *name1, char *name2)
+{
+ time_t tvec;
+
+ time(&tvec);
+ if (!pp->no_formfeed && !pp->tof)
+ (void) write(ofd, pp->form_feed, strlen(pp->form_feed));
+ if (pp->short_banner) { /* short banner only */
+ if (class[0]) {
+ (void) write(ofd, class, strlen(class));
+ (void) write(ofd, ":", 1);
+ }
+ (void) write(ofd, name1, strlen(name1));
+ (void) write(ofd, " Job: ", 7);
+ (void) write(ofd, name2, strlen(name2));
+ (void) write(ofd, " Date: ", 8);
+ (void) write(ofd, ctime(&tvec), 24);
+ (void) write(ofd, "\n", 1);
+ } else { /* normal banner */
+ (void) write(ofd, "\n\n\n", 3);
+ scan_out(pp, ofd, name1, '\0');
+ (void) write(ofd, "\n\n", 2);
+ scan_out(pp, ofd, name2, '\0');
+ if (class[0]) {
+ (void) write(ofd,"\n\n\n",3);
+ scan_out(pp, ofd, class, '\0');
+ }
+ (void) write(ofd, "\n\n\n\n\t\t\t\t\tJob: ", 15);
+ (void) write(ofd, name2, strlen(name2));
+ (void) write(ofd, "\n\t\t\t\t\tDate: ", 12);
+ (void) write(ofd, ctime(&tvec), 24);
+ (void) write(ofd, "\n", 1);
+ }
+ if (!pp->no_formfeed)
+ (void) write(ofd, pp->form_feed, strlen(pp->form_feed));
+ pp->tof = 1;
+}
+
+static char *
+scnline(int key, char *p, int c)
+{
+ register int scnwidth;
+
+ for (scnwidth = WIDTH; --scnwidth;) {
+ key <<= 1;
+ *p++ = key & 0200 ? c : BACKGND;
+ }
+ return (p);
+}
+
+#define TRC(q) (((q)-' ')&0177)
+
+static void
+scan_out(struct printer *pp, int scfd, char *scsp, int dlm)
+{
+ register char *strp;
+ register int nchrs, j;
+ char outbuf[LINELEN+1], *sp, c, cc;
+ int d, scnhgt;
+
+ for (scnhgt = 0; scnhgt++ < HEIGHT+DROP; ) {
+ strp = &outbuf[0];
+ sp = scsp;
+ for (nchrs = 0; ; ) {
+ d = dropit(c = TRC(cc = *sp++));
+ if ((!d && scnhgt > HEIGHT) || (scnhgt <= DROP && d))
+ for (j = WIDTH; --j;)
+ *strp++ = BACKGND;
+ else
+ strp = scnline(scnkey[(int)c][scnhgt-1-d], strp, cc);
+ if (*sp == dlm || *sp == '\0' ||
+ nchrs++ >= pp->page_width/(WIDTH+1)-1)
+ break;
+ *strp++ = BACKGND;
+ *strp++ = BACKGND;
+ }
+ while (*--strp == BACKGND && strp >= outbuf)
+ ;
+ strp++;
+ *strp++ = '\n';
+ (void) write(scfd, outbuf, strp-outbuf);
+ }
+}
+
+static int
+dropit(int c)
+{
+ switch(c) {
+
+ case TRC('_'):
+ case TRC(';'):
+ case TRC(','):
+ case TRC('g'):
+ case TRC('j'):
+ case TRC('p'):
+ case TRC('q'):
+ case TRC('y'):
+ return (DROP);
+
+ default:
+ return (0);
+ }
+}
+
+/*
+ * sendmail ---
+ * tell people about job completion
+ */
+static void
+sendmail(struct printer *pp, char *userid, int bombed)
+{
+ register int i;
+ int p[2], s;
+ register const char *cp;
+ struct stat stb;
+ FILE *fp;
+
+ pipe(p);
+ if ((s = dofork(pp, DORETURN)) == 0) { /* child */
+ dup2(p[0], STDIN_FILENO);
+ closelog();
+ closeallfds(3);
+ if ((cp = strrchr(_PATH_SENDMAIL, '/')) != NULL)
+ cp++;
+ else
+ cp = _PATH_SENDMAIL;
+ execl(_PATH_SENDMAIL, cp, "-t", (char *)0);
+ _exit(0);
+ } else if (s > 0) { /* parent */
+ dup2(p[1], STDOUT_FILENO);
+ printf("To: %s@%s\n", userid, origin_host);
+ printf("Subject: %s printer job \"%s\"\n", pp->printer,
+ *jobname ? jobname : "<unknown>");
+ printf("Reply-To: root@%s\n\n", local_host);
+ printf("Your printer job ");
+ if (*jobname)
+ printf("(%s) ", jobname);
+
+ switch (bombed) {
+ case OK:
+ cp = "OK";
+ printf("\ncompleted successfully\n");
+ break;
+ default:
+ case FATALERR:
+ cp = "FATALERR";
+ printf("\ncould not be printed\n");
+ break;
+ case NOACCT:
+ cp = "NOACCT";
+ printf("\ncould not be printed without an account on %s\n",
+ local_host);
+ break;
+ case FILTERERR:
+ cp = "FILTERERR";
+ if (stat(tempstderr, &stb) < 0 || stb.st_size == 0
+ || (fp = fopen(tempstderr, "r")) == NULL) {
+ printf("\nhad some errors and may not have printed\n");
+ break;
+ }
+ printf("\nhad the following errors and may not have printed:\n");
+ while ((i = getc(fp)) != EOF)
+ putchar(i);
+ (void) fclose(fp);
+ break;
+ case ACCESS:
+ cp = "ACCESS";
+ printf("\nwas not printed because it was not linked to the original file\n");
+ }
+ fflush(stdout);
+ (void) close(STDOUT_FILENO);
+ } else {
+ syslog(LOG_WARNING, "unable to send mail to %s: %m", userid);
+ return;
+ }
+ (void) close(p[0]);
+ (void) close(p[1]);
+ wait(NULL);
+ syslog(LOG_INFO, "mail sent to user %s about job %s on printer %s (%s)",
+ userid, *jobname ? jobname : "<unknown>", pp->printer, cp);
+}
+
+/*
+ * dofork - fork with retries on failure
+ */
+static int
+dofork(const struct printer *pp, int action)
+{
+ pid_t forkpid;
+ int i, fail;
+ struct passwd *pwd;
+
+ forkpid = -1;
+ if (daemon_uname == NULL) {
+ pwd = getpwuid(pp->daemon_user);
+ if (pwd == NULL) {
+ syslog(LOG_ERR, "%s: Can't lookup default daemon uid (%ld) in password file",
+ pp->printer, pp->daemon_user);
+ goto error_ret;
+ }
+ daemon_uname = strdup(pwd->pw_name);
+ daemon_defgid = pwd->pw_gid;
+ }
+
+ for (i = 0; i < 20; i++) {
+ forkpid = fork();
+ if (forkpid < 0) {
+ sleep((unsigned)(i*i));
+ continue;
+ }
+ /*
+ * Child should run as daemon instead of root
+ */
+ if (forkpid == 0) {
+ errno = 0;
+ fail = initgroups(daemon_uname, daemon_defgid);
+ if (fail) {
+ syslog(LOG_ERR, "%s: initgroups(%s,%u): %m",
+ pp->printer, daemon_uname, daemon_defgid);
+ break;
+ }
+ fail = setgid(daemon_defgid);
+ if (fail) {
+ syslog(LOG_ERR, "%s: setgid(%u): %m",
+ pp->printer, daemon_defgid);
+ break;
+ }
+ fail = setuid(pp->daemon_user);
+ if (fail) {
+ syslog(LOG_ERR, "%s: setuid(%ld): %m",
+ pp->printer, pp->daemon_user);
+ break;
+ }
+ }
+ return (forkpid);
+ }
+
+ /*
+ * An error occurred. If the error is in the child process, then
+ * this routine MUST always exit(). DORETURN only effects how
+ * errors should be handled in the parent process.
+ */
+error_ret:
+ if (forkpid == 0) {
+ syslog(LOG_ERR, "%s: dofork(): aborting child process...",
+ pp->printer);
+ exit(1);
+ }
+ syslog(LOG_ERR, "%s: dofork(): failure in fork", pp->printer);
+
+ sleep(1); /* throttle errors, as a safety measure */
+ switch (action) {
+ case DORETURN:
+ return (-1);
+ default:
+ syslog(LOG_ERR, "bad action (%d) to dofork", action);
+ /* FALLTHROUGH */
+ case DOABORT:
+ exit(1);
+ }
+ /*NOTREACHED*/
+}
+
+/*
+ * Kill child processes to abort current job.
+ */
+static void
+abortpr(int signo __unused)
+{
+
+ (void) unlink(tempstderr);
+ kill(0, SIGINT);
+ if (of_pid > 0)
+ kill(of_pid, SIGCONT);
+ while (wait(NULL) > 0)
+ ;
+ if (of_pid > 0 && tfd != -1)
+ unlink(tfile);
+ exit(0);
+}
+
+static void
+init(struct printer *pp)
+{
+ char *s;
+
+ sprintf(&width[2], "%ld", pp->page_width);
+ sprintf(&length[2], "%ld", pp->page_length);
+ sprintf(&pxwidth[2], "%ld", pp->page_pwidth);
+ sprintf(&pxlength[2], "%ld", pp->page_plength);
+ if ((s = checkremote(pp)) != NULL) {
+ syslog(LOG_WARNING, "%s", s);
+ free(s);
+ }
+}
+
+void
+startprinting(const char *printer)
+{
+ struct printer myprinter, *pp = &myprinter;
+ int status;
+
+ init_printer(pp);
+ status = getprintcap(printer, pp);
+ switch(status) {
+ case PCAPERR_OSERR:
+ syslog(LOG_ERR, "can't open printer description file: %m");
+ exit(1);
+ case PCAPERR_NOTFOUND:
+ syslog(LOG_ERR, "unknown printer: %s", printer);
+ exit(1);
+ case PCAPERR_TCLOOP:
+ fatal(pp, "potential reference loop detected in printcap file");
+ default:
+ break;
+ }
+ printjob(pp);
+}
+
+/*
+ * Acquire line printer or remote connection.
+ */
+static void
+openpr(const struct printer *pp)
+{
+ int p[2];
+ char *cp;
+
+ if (pp->remote) {
+ openrem(pp);
+ /*
+ * Lpd does support the setting of 'of=' filters for
+ * jobs going to remote machines, but that does not
+ * have the same meaning as 'of=' does when handling
+ * local print queues. For remote machines, all 'of='
+ * filter processing is handled in sendfile(), and that
+ * does not use these global "output filter" variables.
+ */
+ ofd = -1;
+ of_pid = 0;
+ return;
+ } else if (*pp->lp) {
+ if (strchr(pp->lp, '@') != NULL)
+ opennet(pp);
+ else
+ opentty(pp);
+ } else {
+ syslog(LOG_ERR, "%s: no line printer device or host name",
+ pp->printer);
+ exit(1);
+ }
+
+ /*
+ * Start up an output filter, if needed.
+ */
+ if (pp->filters[LPF_OUTPUT] && !pp->filters[LPF_INPUT] && !of_pid) {
+ pipe(p);
+ if (pp->remote) {
+ strcpy(tfile, TFILENAME);
+ tfd = mkstemp(tfile);
+ }
+ if ((of_pid = dofork(pp, DOABORT)) == 0) { /* child */
+ dup2(p[0], STDIN_FILENO); /* pipe is std in */
+ /* tfile/printer is stdout */
+ dup2(pp->remote ? tfd : pfd, STDOUT_FILENO);
+ closelog();
+ closeallfds(3);
+ if ((cp = strrchr(pp->filters[LPF_OUTPUT], '/')) == NULL)
+ cp = pp->filters[LPF_OUTPUT];
+ else
+ cp++;
+ execl(pp->filters[LPF_OUTPUT], cp, width, length,
+ (char *)0);
+ syslog(LOG_ERR, "%s: execl(%s): %m", pp->printer,
+ pp->filters[LPF_OUTPUT]);
+ exit(1);
+ }
+ (void) close(p[0]); /* close input side */
+ ofd = p[1]; /* use pipe for output */
+ } else {
+ ofd = pfd;
+ of_pid = 0;
+ }
+}
+
+/*
+ * Printer connected directly to the network
+ * or to a terminal server on the net
+ */
+static void
+opennet(const struct printer *pp)
+{
+ register int i;
+ int resp;
+ u_long port;
+ char *ep;
+ void (*savealrm)(int);
+
+ port = strtoul(pp->lp, &ep, 0);
+ if (*ep != '@' || port > 65535) {
+ syslog(LOG_ERR, "%s: bad port number: %s", pp->printer,
+ pp->lp);
+ exit(1);
+ }
+ ep++;
+
+ for (i = 1; ; i = i < 256 ? i << 1 : i) {
+ resp = -1;
+ savealrm = signal(SIGALRM, alarmhandler);
+ alarm(pp->conn_timeout);
+ pfd = getport(pp, ep, port);
+ alarm(0);
+ (void)signal(SIGALRM, savealrm);
+ if (pfd < 0 && errno == ECONNREFUSED)
+ resp = 1;
+ else if (pfd >= 0) {
+ /*
+ * need to delay a bit for rs232 lines
+ * to stabilize in case printer is
+ * connected via a terminal server
+ */
+ delay(500);
+ break;
+ }
+ if (i == 1) {
+ if (resp < 0)
+ pstatus(pp, "waiting for %s to come up",
+ pp->lp);
+ else
+ pstatus(pp,
+ "waiting for access to printer on %s",
+ pp->lp);
+ }
+ sleep(i);
+ }
+ pstatus(pp, "sending to %s port %lu", ep, port);
+}
+
+/*
+ * Printer is connected to an RS232 port on this host
+ */
+static void
+opentty(const struct printer *pp)
+{
+ register int i;
+
+ for (i = 1; ; i = i < 32 ? i << 1 : i) {
+ pfd = open(pp->lp, pp->rw ? O_RDWR : O_WRONLY);
+ if (pfd >= 0) {
+ delay(500);
+ break;
+ }
+ if (errno == ENOENT) {
+ syslog(LOG_ERR, "%s: %m", pp->lp);
+ exit(1);
+ }
+ if (i == 1)
+ pstatus(pp,
+ "waiting for %s to become ready (offline?)",
+ pp->printer);
+ sleep(i);
+ }
+ if (isatty(pfd))
+ setty(pp);
+ pstatus(pp, "%s is ready and printing", pp->printer);
+}
+
+/*
+ * Printer is on a remote host
+ */
+static void
+openrem(const struct printer *pp)
+{
+ register int i;
+ int resp;
+ void (*savealrm)(int);
+
+ for (i = 1; ; i = i < 256 ? i << 1 : i) {
+ resp = -1;
+ savealrm = signal(SIGALRM, alarmhandler);
+ alarm(pp->conn_timeout);
+ pfd = getport(pp, pp->remote_host, 0);
+ alarm(0);
+ (void)signal(SIGALRM, savealrm);
+ if (pfd >= 0) {
+ if ((writel(pfd, "\2", pp->remote_queue, "\n",
+ (char *)0)
+ == 2 + strlen(pp->remote_queue))
+ && (resp = response(pp)) == 0)
+ break;
+ (void) close(pfd);
+ }
+ if (i == 1) {
+ if (resp < 0)
+ pstatus(pp, "waiting for %s to come up",
+ pp->remote_host);
+ else {
+ pstatus(pp,
+ "waiting for queue to be enabled on %s",
+ pp->remote_host);
+ i = 256;
+ }
+ }
+ sleep(i);
+ }
+ pstatus(pp, "sending to %s", pp->remote_host);
+}
+
+/*
+ * setup tty lines.
+ */
+static void
+setty(const struct printer *pp)
+{
+ struct termios ttybuf;
+
+ if (ioctl(pfd, TIOCEXCL, (char *)0) < 0) {
+ syslog(LOG_ERR, "%s: ioctl(TIOCEXCL): %m", pp->printer);
+ exit(1);
+ }
+ if (tcgetattr(pfd, &ttybuf) < 0) {
+ syslog(LOG_ERR, "%s: tcgetattr: %m", pp->printer);
+ exit(1);
+ }
+ if (pp->baud_rate > 0)
+ cfsetspeed(&ttybuf, pp->baud_rate);
+ if (pp->mode_set) {
+ char *s = strdup(pp->mode_set), *tmp;
+
+ while ((tmp = strsep(&s, ",")) != NULL) {
+ (void) msearch(tmp, &ttybuf);
+ }
+ }
+ if (pp->mode_set != 0 || pp->baud_rate > 0) {
+ if (tcsetattr(pfd, TCSAFLUSH, &ttybuf) == -1) {
+ syslog(LOG_ERR, "%s: tcsetattr: %m", pp->printer);
+ }
+ }
+}
+
+#include <stdarg.h>
+
+static void
+pstatus(const struct printer *pp, const char *msg, ...)
+{
+ int fd;
+ char *buf;
+ va_list ap;
+ va_start(ap, msg);
+
+ umask(S_IWOTH);
+ fd = open(pp->status_file, O_WRONLY|O_CREAT|O_EXLOCK, STAT_FILE_MODE);
+ if (fd < 0) {
+ syslog(LOG_ERR, "%s: open(%s): %m", pp->printer,
+ pp->status_file);
+ exit(1);
+ }
+ ftruncate(fd, 0);
+ vasprintf(&buf, msg, ap);
+ va_end(ap);
+ writel(fd, buf, "\n", (char *)0);
+ close(fd);
+ free(buf);
+}
+
+void
+alarmhandler(int signo __unused)
+{
+ /* the signal is ignored */
+ /* (the '__unused' is just to avoid a compile-time warning) */
+}
diff --git a/usr.sbin/lpr/lpd/recvjob.c b/usr.sbin/lpr/lpd/recvjob.c
new file mode 100644
index 000000000000..f103829b19e8
--- /dev/null
+++ b/usr.sbin/lpr/lpd/recvjob.c
@@ -0,0 +1,393 @@
+/*-
+ * SPDX-License-Identifier: BSD-3-Clause
+ *
+ * Copyright (c) 1983, 1993
+ * The Regents of the University of California. All rights reserved.
+ *
+ *
+ * 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 "lp.cdefs.h" /* A cross-platform version of <sys/cdefs.h> */
+/*
+ * Receive printer jobs from the network, queue them and
+ * start the printer daemon.
+ */
+#include <sys/param.h>
+#include <sys/mount.h>
+#include <sys/stat.h>
+
+#include <unistd.h>
+#include <signal.h>
+#include <fcntl.h>
+#include <dirent.h>
+#include <errno.h>
+#include <syslog.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include "lp.h"
+#include "lp.local.h"
+#include "ctlinfo.h"
+#include "extern.h"
+#include "pathnames.h"
+
+#define ack() (void) write(STDOUT_FILENO, sp, (size_t)1)
+
+/*
+ * The buffer size to use when reading/writing spool files.
+ */
+#define SPL_BUFSIZ BUFSIZ
+
+static char dfname[NAME_MAX]; /* data files */
+static int minfree; /* keep at least minfree blocks available */
+static const char *sp = "";
+static char tfname[NAME_MAX]; /* tmp copy of cf before linking */
+
+static int chksize(int _size);
+static void frecverr(const char *_msg, ...) __printf0like(1, 2);
+static int noresponse(void);
+static void rcleanup(int _signo);
+static int read_number(const char *_fn);
+static int readfile(struct printer *_pp, char *_file, size_t _size);
+static int readjob(struct printer *_pp);
+
+
+void
+recvjob(const char *printer)
+{
+ struct stat stb;
+ int status;
+ struct printer myprinter, *pp = &myprinter;
+
+ /*
+ * Perform lookup for printer name or abbreviation
+ */
+ init_printer(pp);
+ status = getprintcap(printer, pp);
+ switch (status) {
+ case PCAPERR_OSERR:
+ frecverr("cannot open printer description file");
+ break;
+ case PCAPERR_NOTFOUND:
+ frecverr("unknown printer %s", printer);
+ break;
+ case PCAPERR_TCLOOP:
+ fatal(pp, "potential reference loop detected in printcap file");
+ default:
+ break;
+ }
+
+ (void) close(STDERR_FILENO); /* set up log file */
+ if (open(pp->log_file, O_WRONLY|O_APPEND, 0664) < 0) {
+ syslog(LOG_ERR, "%s: %m", pp->log_file);
+ (void) open(_PATH_DEVNULL, O_WRONLY);
+ }
+
+ if (chdir(pp->spool_dir) < 0)
+ frecverr("%s: chdir(%s): %s", pp->printer, pp->spool_dir,
+ strerror(errno));
+ if (stat(pp->lock_file, &stb) == 0) {
+ if (stb.st_mode & 010) {
+ /* queue is disabled */
+ putchar('\1'); /* return error code */
+ exit(1);
+ }
+ } else if (stat(pp->spool_dir, &stb) < 0)
+ frecverr("%s: stat(%s): %s", pp->printer, pp->spool_dir,
+ strerror(errno));
+ minfree = 2 * read_number("minfree"); /* scale KB to 512 blocks */
+ signal(SIGTERM, rcleanup);
+ signal(SIGPIPE, rcleanup);
+
+ if (readjob(pp))
+ printjob(pp);
+}
+
+/*
+ * Read printer jobs sent by lpd and copy them to the spooling directory.
+ * Return the number of jobs successfully transferred.
+ */
+static int
+readjob(struct printer *pp)
+{
+ register int size;
+ int cfcnt, dfcnt;
+ char *cp, *clastp, *errmsg;
+ char givenid[32], givenhost[MAXHOSTNAMELEN];
+
+ ack();
+ cfcnt = 0;
+ dfcnt = 0;
+ for (;;) {
+ /*
+ * Read a command to tell us what to do
+ */
+ cp = line;
+ clastp = line + sizeof(line) - 1;
+ do {
+ size = read(STDOUT_FILENO, cp, (size_t)1);
+ if (size != (ssize_t)1) {
+ if (size < (ssize_t)0) {
+ frecverr("%s: lost connection",
+ pp->printer);
+ /*NOTREACHED*/
+ }
+ return (cfcnt);
+ }
+ } while ((*cp++ != '\n') && (cp <= clastp));
+ if (cp > clastp) {
+ frecverr("%s: readjob overflow", pp->printer);
+ /*NOTREACHED*/
+ }
+ *--cp = '\0';
+ cp = line;
+ switch (*cp++) {
+ case '\1': /* cleanup because data sent was bad */
+ rcleanup(0);
+ continue;
+
+ case '\2': /* read cf file */
+ size = 0;
+ dfcnt = 0;
+ while (*cp >= '0' && *cp <= '9')
+ size = size * 10 + (*cp++ - '0');
+ if (*cp++ != ' ')
+ break;
+ /*
+ * host name has been authenticated, we use our
+ * view of the host name since we may be passed
+ * something different than what gethostbyaddr()
+ * returns
+ */
+ strlcpy(cp + 6, from_host, sizeof(line)
+ + (size_t)(line - cp - 6));
+ if (strchr(cp, '/')) {
+ frecverr("readjob: %s: illegal path name", cp);
+ /*NOTREACHED*/
+ }
+ strlcpy(tfname, cp, sizeof(tfname));
+ tfname[sizeof (tfname) - 1] = '\0';
+ tfname[0] = 't';
+ if (!chksize(size)) {
+ (void) write(STDOUT_FILENO, "\2", (size_t)1);
+ continue;
+ }
+ if (!readfile(pp, tfname, (size_t)size)) {
+ rcleanup(0);
+ continue;
+ }
+ errmsg = ctl_renametf(pp->printer, tfname);
+ tfname[0] = '\0';
+ if (errmsg != NULL) {
+ frecverr("%s: %s", pp->printer, errmsg);
+ /*NOTREACHED*/
+ }
+ cfcnt++;
+ continue;
+
+ case '\3': /* read df file */
+ *givenid = '\0';
+ *givenhost = '\0';
+ size = 0;
+ while (*cp >= '0' && *cp <= '9')
+ size = size * 10 + (*cp++ - '0');
+ if (*cp++ != ' ')
+ break;
+ if (strchr(cp, '/')) {
+ frecverr("readjob: %s: illegal path name", cp);
+ /*NOTREACHED*/
+ }
+ if (!chksize(size)) {
+ (void) write(STDOUT_FILENO, "\2", (size_t)1);
+ continue;
+ }
+ strlcpy(dfname, cp, sizeof(dfname));
+ dfcnt++;
+ trstat_init(pp, dfname, dfcnt);
+ (void) readfile(pp, dfname, (size_t)size);
+ trstat_write(pp, TR_RECVING, (size_t)size, givenid,
+ from_host, givenhost);
+ continue;
+ }
+ frecverr("protocol screwup: %s", line);
+ /*NOTREACHED*/
+ }
+}
+
+/*
+ * Read files send by lpd and copy them to the spooling directory.
+ */
+static int
+readfile(struct printer *pp, char *file, size_t size)
+{
+ register char *cp;
+ char buf[SPL_BUFSIZ];
+ size_t amt, i;
+ int err, fd, j;
+
+ fd = open(file, O_CREAT|O_EXCL|O_WRONLY, FILMOD);
+ if (fd < 0) {
+ frecverr("%s: readfile: error on open(%s): %s",
+ pp->printer, file, strerror(errno));
+ /*NOTREACHED*/
+ }
+ ack();
+ err = 0;
+ for (i = 0; i < size; i += SPL_BUFSIZ) {
+ amt = SPL_BUFSIZ;
+ cp = buf;
+ if (i + amt > size)
+ amt = size - i;
+ do {
+ j = read(STDOUT_FILENO, cp, amt);
+ if (j <= 0) {
+ frecverr("%s: lost connection", pp->printer);
+ /*NOTREACHED*/
+ }
+ amt -= j;
+ cp += j;
+ } while (amt > 0);
+ amt = SPL_BUFSIZ;
+ if (i + amt > size)
+ amt = size - i;
+ if (write(fd, buf, amt) != (ssize_t)amt) {
+ err++;
+ break;
+ }
+ }
+ (void) close(fd);
+ if (err) {
+ frecverr("%s: write error on close(%s)", pp->printer, file);
+ /*NOTREACHED*/
+ }
+ if (noresponse()) { /* file sent had bad data in it */
+ if (strchr(file, '/') == NULL)
+ (void) unlink(file);
+ return (0);
+ }
+ ack();
+ return (1);
+}
+
+static int
+noresponse(void)
+{
+ char resp;
+
+ if (read(STDOUT_FILENO, &resp, (size_t)1) != 1) {
+ frecverr("lost connection in noresponse()");
+ /*NOTREACHED*/
+ }
+ if (resp == '\0')
+ return(0);
+ return(1);
+}
+
+/*
+ * Check to see if there is enough space on the disk for size bytes.
+ * 1 == OK, 0 == Not OK.
+ */
+static int
+chksize(int size)
+{
+ int64_t spacefree;
+ struct statfs sfb;
+
+ if (statfs(".", &sfb) < 0) {
+ syslog(LOG_ERR, "%s: %m", "statfs(\".\")");
+ return (1);
+ }
+ spacefree = sfb.f_bavail * (sfb.f_bsize / 512);
+ size = (size + 511) / 512;
+ if (minfree + size > spacefree)
+ return(0);
+ return(1);
+}
+
+static int
+read_number(const char *fn)
+{
+ char lin[80];
+ register FILE *fp;
+
+ if ((fp = fopen(fn, "r")) == NULL)
+ return (0);
+ if (fgets(lin, sizeof(lin), fp) == NULL) {
+ fclose(fp);
+ return (0);
+ }
+ fclose(fp);
+ return (atoi(lin));
+}
+
+/*
+ * Remove all the files associated with the current job being transferred.
+ */
+static void
+rcleanup(int signo __unused)
+{
+ if (tfname[0] && strchr(tfname, '/') == NULL)
+ (void) unlink(tfname);
+ if (dfname[0] && strchr(dfname, '/') == NULL) {
+ do {
+ do
+ (void) unlink(dfname);
+ while (dfname[2]-- != 'A');
+ dfname[2] = 'z';
+ } while (dfname[0]-- != 'd');
+ }
+ dfname[0] = '\0';
+}
+
+#include <stdarg.h>
+
+static void
+frecverr(const char *msg, ...)
+{
+ va_list ap;
+ va_start(ap, msg);
+ syslog(LOG_ERR, "Error receiving job from %s:", from_host);
+ vsyslog(LOG_ERR, msg, ap);
+ va_end(ap);
+ /*
+ * rcleanup is not called until AFTER logging the error message,
+ * because rcleanup will zap some variables which may have been
+ * supplied as parameters for that msg...
+ */
+ rcleanup(0);
+ /*
+ * Add a minimal delay before returning the final error code to
+ * the sending host. This just in case that machine responds
+ * this error by INSTANTLY retrying (and instantly re-failing...).
+ * It would be stupid of the sending host to do that, but if there
+ * was a broken implementation which did it, the result might be
+ * obscure performance problems and a flood of syslog messages on
+ * the receiving host.
+ */
+ sleep(2); /* a paranoid throttling measure */
+ putchar('\1'); /* return error code */
+ exit(1);
+}
diff --git a/usr.sbin/lpr/lpq/Makefile b/usr.sbin/lpr/lpq/Makefile
new file mode 100644
index 000000000000..2e29a0386204
--- /dev/null
+++ b/usr.sbin/lpr/lpq/Makefile
@@ -0,0 +1,13 @@
+BINDIR= /usr/bin
+
+PACKAGE=lp
+PROG= lpq
+BINOWN= root
+BINGRP= daemon
+BINMODE= 6555
+
+CFLAGS+= -I${.CURDIR:H}/common_source
+
+LIBADD= lpr
+
+.include <bsd.prog.mk>
diff --git a/usr.sbin/lpr/lpq/Makefile.depend b/usr.sbin/lpr/lpq/Makefile.depend
new file mode 100644
index 000000000000..52597b984452
--- /dev/null
+++ b/usr.sbin/lpr/lpq/Makefile.depend
@@ -0,0 +1,16 @@
+# Autogenerated - do NOT edit!
+
+DIRDEPS = \
+ include \
+ include/xlocale \
+ lib/${CSU_DIR} \
+ lib/libc \
+ lib/libcompiler_rt \
+ usr.sbin/lpr/common_source \
+
+
+.include <dirdeps.mk>
+
+.if ${DEP_RELDIR} == ${_DEP_RELDIR}
+# local dependencies - needed for -jN in clean tree
+.endif
diff --git a/usr.sbin/lpr/lpq/lpq.1 b/usr.sbin/lpr/lpq/lpq.1
new file mode 100644
index 000000000000..c87bcbbaa94d
--- /dev/null
+++ b/usr.sbin/lpr/lpq/lpq.1
@@ -0,0 +1,135 @@
+.\" Copyright (c) 1983, 1990, 1993
+.\" The Regents of the University of California. All rights reserved.
+.\"
+.\" 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.
+.\"
+.Dd April 28, 1995
+.Dt LPQ 1
+.Os
+.Sh NAME
+.Nm lpq
+.Nd spool queue examination program
+.Sh SYNOPSIS
+.Nm
+.Op Fl a
+.Op Fl l
+.Op Fl P Ns Ar printer
+.Op job # ...\&
+.Op user ...\&
+.Sh DESCRIPTION
+The
+.Nm
+utility examines the spooling area used by
+.Xr lpd 8
+for printing files on the line printer, and reports the status of the
+specified jobs or all jobs associated with a user.
+The
+.Nm
+utility invoked
+without any arguments reports on any jobs currently in the queue.
+.Pp
+Options:
+.Bl -tag -width indent
+.It Fl P
+Specify a particular printer, otherwise the default
+line printer is used (or the value of the
+.Ev PRINTER
+variable in the
+environment).
+All other arguments supplied are interpreted as user
+names or job numbers to filter out only those jobs of interest.
+.It Fl l
+Information about each of the files comprising the job entry
+is printed.
+Normally, only as much information as will fit on one line is displayed.
+.It Fl a
+Report on the local queues for all printers,
+rather than just the specified printer.
+.El
+.Pp
+For each job submitted (i.e., invocation of
+.Xr lpr 1 )
+.Nm
+reports the user's name, current rank in the queue, the
+names of files comprising the job, the job identifier (a number which
+may be supplied to
+.Xr lprm 1
+for removing a specific job), and the total size in bytes.
+Job ordering is dependent on
+the algorithm used to scan the spooling directory and is supposed
+to be
+.Tn FIFO
+(First in First Out).
+File names comprising a job may be unavailable
+(when
+.Xr lpr 1
+is used as a sink in a pipeline) in which case the file
+is indicated as ``(standard input)''.
+.Pp
+If
+.Nm
+warns that there is no daemon present (i.e., due to some malfunction),
+the
+.Xr lpc 8
+command can be used to restart the printer daemon.
+.Sh ENVIRONMENT
+If the following environment variable exists, it is used by
+.Nm :
+.Bl -tag -width PRINTER
+.It Ev PRINTER
+Specifies an alternate default printer.
+.El
+.Sh FILES
+.Bl -tag -width "/var/spool/*/lock" -compact
+.It Pa /etc/printcap
+To determine printer characteristics.
+.It Pa /var/spool/*
+The spooling directory, as determined from printcap.
+.It Pa /var/spool/*/cf*
+Control files specifying jobs.
+.It Pa /var/spool/*/lock
+The lock file to obtain the currently active job.
+.El
+.Sh DIAGNOSTICS
+Unable to open various files.
+The lock file being malformed.
+Garbage
+files when there is no daemon active, but files in the spooling directory.
+.Sh SEE ALSO
+.Xr lpr 1 ,
+.Xr lprm 1 ,
+.Xr lpc 8 ,
+.Xr lpd 8
+.Sh HISTORY
+A
+.Nm
+utility appeared in
+.Bx 3 .
+.Sh BUGS
+Due to the dynamic nature of the information in the spooling directory
+.Nm
+may report unreliably.
+Output formatting is sensitive to the line length of the terminal;
+this can results in widely spaced columns.
diff --git a/usr.sbin/lpr/lpq/lpq.c b/usr.sbin/lpr/lpq/lpq.c
new file mode 100644
index 000000000000..6899b691ee3a
--- /dev/null
+++ b/usr.sbin/lpr/lpq/lpq.c
@@ -0,0 +1,183 @@
+/*-
+ * SPDX-License-Identifier: BSD-3-Clause
+ *
+ * Copyright (c) 1983, 1993
+ * The Regents of the University of California. All rights reserved.
+ *
+ *
+ * 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 "lp.cdefs.h" /* A cross-platform version of <sys/cdefs.h> */
+/*
+ * Spool Queue examination program
+ *
+ * lpq [-a] [-l] [-Pprinter] [user...] [job...]
+ *
+ * -a show all non-null queues on the local machine
+ * -l long output
+ * -P used to identify printer as per lpr/lprm
+ */
+
+#include <sys/param.h>
+
+#include <ctype.h>
+#include <dirent.h>
+#include <err.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <syslog.h>
+#include <unistd.h>
+
+#include "lp.h"
+#include "lp.local.h"
+#include "pathnames.h"
+
+int requ[MAXREQUESTS]; /* job number of spool entries */
+int requests; /* # of spool requests */
+char *user[MAXUSERS]; /* users to process */
+int users; /* # of users in user array */
+
+uid_t uid, euid;
+
+static int ckqueue(const struct printer *_pp);
+static void usage(void);
+int main(int _argc, char **_argv);
+
+int
+main(int argc, char **argv)
+{
+ int ch, aflag, lflag;
+ const char *printer;
+ struct printer myprinter, *pp = &myprinter;
+
+ printer = NULL;
+ euid = geteuid();
+ uid = getuid();
+ PRIV_END
+ progname = *argv;
+ if (gethostname(local_host, sizeof(local_host)))
+ err(1, "gethostname");
+ openlog("lpd", 0, LOG_LPR);
+
+ aflag = lflag = 0;
+ while ((ch = getopt(argc, argv, "alP:")) != -1)
+ switch((char)ch) {
+ case 'a':
+ ++aflag;
+ break;
+ case 'l': /* long output */
+ ++lflag;
+ break;
+ case 'P': /* printer name */
+ printer = optarg;
+ break;
+ case '?':
+ default:
+ usage();
+ }
+
+ if (!aflag && printer == NULL && (printer = getenv("PRINTER")) == NULL)
+ printer = DEFLP;
+
+ for (argc -= optind, argv += optind; argc; --argc, ++argv)
+ if (isdigit(argv[0][0])) {
+ if (requests >= MAXREQUESTS)
+ fatal(0, "too many requests");
+ requ[requests++] = atoi(*argv);
+ }
+ else {
+ if (users >= MAXUSERS)
+ fatal(0, "too many users");
+ user[users++] = *argv;
+ }
+
+ if (aflag) {
+ int more, status;
+
+ more = firstprinter(pp, &status);
+ if (status)
+ goto looperr;
+ while (more) {
+ if (ckqueue(pp) > 0) {
+ printf("%s:\n", pp->printer);
+ displayq(pp, lflag);
+ printf("\n");
+ }
+ do {
+ more = nextprinter(pp, &status);
+looperr:
+ switch (status) {
+ case PCAPERR_TCOPEN:
+ printf("warning: %s: unresolved "
+ "tc= reference(s) ",
+ pp->printer);
+ case PCAPERR_SUCCESS:
+ break;
+ default:
+ fatal(pp, "%s", pcaperr(status));
+ }
+ } while (more && status);
+ }
+ } else {
+ int status;
+
+ init_printer(pp);
+ status = getprintcap(printer, pp);
+ if (status < 0)
+ fatal(pp, "%s", pcaperr(status));
+
+ displayq(pp, lflag);
+ }
+ exit(0);
+}
+
+static int
+ckqueue(const struct printer *pp)
+{
+ register struct dirent *d;
+ DIR *dirp;
+ char *spooldir;
+
+ spooldir = pp->spool_dir;
+ if ((dirp = opendir(spooldir)) == NULL)
+ return (-1);
+ while ((d = readdir(dirp)) != NULL) {
+ if (d->d_name[0] != 'c' || d->d_name[1] != 'f')
+ continue; /* daemon control files only */
+ closedir(dirp);
+ return (1); /* found something */
+ }
+ closedir(dirp);
+ return (0);
+}
+
+static void
+usage(void)
+{
+ fprintf(stderr,
+ "usage: lpq [-a] [-l] [-Pprinter] [user ...] [job ...]\n");
+ exit(1);
+}
diff --git a/usr.sbin/lpr/lpr/Makefile b/usr.sbin/lpr/lpr/Makefile
new file mode 100644
index 000000000000..2c3b14874e7d
--- /dev/null
+++ b/usr.sbin/lpr/lpr/Makefile
@@ -0,0 +1,18 @@
+.PATH: ${.CURDIR:H}/common_source
+
+BINDIR= /usr/bin
+
+PACKAGE=lp
+PROG= lpr
+MAN= lpr.1 printcap.5
+BINOWN= root
+BINGRP= daemon
+BINMODE= 6555
+
+CFLAGS+= -I${.CURDIR:H}/common_source
+
+WARNS?= 2
+
+LIBADD= lpr
+
+.include <bsd.prog.mk>
diff --git a/usr.sbin/lpr/lpr/Makefile.depend b/usr.sbin/lpr/lpr/Makefile.depend
new file mode 100644
index 000000000000..52597b984452
--- /dev/null
+++ b/usr.sbin/lpr/lpr/Makefile.depend
@@ -0,0 +1,16 @@
+# Autogenerated - do NOT edit!
+
+DIRDEPS = \
+ include \
+ include/xlocale \
+ lib/${CSU_DIR} \
+ lib/libc \
+ lib/libcompiler_rt \
+ usr.sbin/lpr/common_source \
+
+
+.include <dirdeps.mk>
+
+.if ${DEP_RELDIR} == ${_DEP_RELDIR}
+# local dependencies - needed for -jN in clean tree
+.endif
diff --git a/usr.sbin/lpr/lpr/lpr.1 b/usr.sbin/lpr/lpr/lpr.1
new file mode 100644
index 000000000000..8acb9d23d9da
--- /dev/null
+++ b/usr.sbin/lpr/lpr/lpr.1
@@ -0,0 +1,318 @@
+.\" Copyright (c) 1980, 1990, 1993
+.\" The Regents of the University of California. All rights reserved.
+.\"
+.\" 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.
+.\" "
+.Dd June 6, 1993
+.Dt LPR 1
+.Os
+.Sh NAME
+.Nm lpr
+.Nd off line print
+.Sh SYNOPSIS
+.Nm
+.Op Fl P Ns Ar printer
+.Op Fl \&# Ns Ar num
+.Op Fl C Ar class
+.Op Fl J Ar job
+.Op Fl L Ar locale
+.Op Fl T Ar title
+.Op Fl U Ar user
+.Op Fl Z Ar daemon-options
+.Op Fl i Ar numcols
+.Op Fl 1234 Ar font
+.Op Fl w Ar num
+.Op Fl cdfghlnmprstv
+.Op Ar name ...
+.Sh DESCRIPTION
+The
+.Nm
+utility uses a spooling daemon to print the named files when facilities
+become available.
+If no names appear, the standard input is assumed.
+.Pp
+The following single letter options are used to notify the line printer
+spooler that the files are not standard text files.
+The spooling daemon will
+use the appropriate filters to print the data accordingly.
+Note that not all spoolers implement filters for all data types,
+and some sites may use these types for other purposes than the ones
+described here.
+.Bl -tag -width indent
+.It Fl d
+The files are assumed to contain data in
+.Tn DVI
+format from the
+.Tn TeX
+typesetting system.
+.It Fl f
+Use a filter which interprets the first character of each line as a
+standard
+.Tn FORTRAN
+carriage control character.
+.It Fl l
+Use a filter which allows control characters to be printed and suppresses
+page breaks.
+.It Fl p
+Use
+.Xr pr 1
+to format the files.
+.El
+.Pp
+The following options are historical and not directly supported by any
+software included in
+.Fx .
+.Bl -tag -width indent
+.It Fl c
+The files are assumed to contain data produced by
+.Xr cifplot 1 .
+.It Fl g
+The files are assumed to contain standard plot data as produced by the
+.Ux
+.Xr plot 3
+routines.
+.It Fl n
+The files are assumed to contain data from
+.Em ditroff
+(device independent troff).
+.It Fl t
+The files are assumed to contain
+.Tn C/A/T
+phototypesetter commands from ancient versions of
+.Ux
+.Xr troff 1 Pq Pa ports/textproc/groff .
+.It Fl v
+The files are assumed to contain a raster image for devices like the
+Benson Varian.
+.El
+.Pp
+These options apply to the handling of
+the print job:
+.Bl -tag -width indent
+.It Fl P
+Force output to a specific printer.
+Normally,
+the default printer is used (site dependent), or the value of the
+environment variable
+.Ev PRINTER
+is used.
+.It Fl h
+Suppress the printing of the burst page.
+.It Fl m
+Send mail upon completion.
+.It Fl r
+Remove the file upon completion of spooling or upon completion of
+printing (with the
+.Fl s
+option).
+.It Fl s
+Use symbolic links.
+Usually files are copied to the spool directory.
+The
+.Fl s
+option will use
+.Xr symlink 2
+to link data files rather than trying to copy them so large files can be
+printed.
+This means the files should
+not be modified or removed until they have been printed.
+.El
+.Pp
+The remaining options apply to copies, the page display, and headers:
+.Bl -tag -width indent
+.It Fl \&# Ns Ar num
+The quantity
+.Ar num
+is the number of copies desired of each file named.
+For example,
+.Bd -literal -offset indent
+lpr \-#3 foo.c bar.c more.c
+.Ed
+would result in 3 copies of the file foo.c, followed by 3 copies
+of the file bar.c, etc.
+On the other hand,
+.Bd -literal -offset indent
+cat foo.c bar.c more.c \&| lpr \-#3
+.Ed
+.Pp
+will give three copies of the concatenation of the files.
+Often
+a site will disable this feature to encourage use of a photocopier
+instead.
+.It Xo
+.Fl Ns Op Cm 1234
+.Ar font
+.Xc
+Specifies a
+.Ar font
+to be mounted on font position
+.Ar i .
+The daemon
+will construct a
+.Li .railmag
+file referencing
+the font pathname.
+.It Fl C Ar class
+Job classification
+to use on the burst page.
+For example,
+.Bd -literal -offset indent
+lpr \-C EECS foo.c
+.Ed
+.Pp
+causes the system name (the name returned by
+.Xr hostname 1 )
+to be replaced on the burst page by
+.Tn EECS ,
+and the file foo.c to be printed.
+.It Fl J Ar job
+Job name to print on the burst page.
+Normally, the first file's name is used.
+.It Fl L Ar locale
+Use
+.Ar locale
+specified as argument instead of one found in environment.
+(Only effective when filtering through
+.Xr pr 1
+is requested using the
+.Fl p
+option.)
+.It Fl T Ar title
+Title name for
+.Xr pr 1 ,
+instead of the file name.
+.It Fl U Ar user
+User name to print on the burst page,
+also for accounting purposes.
+This option is only honored if the real user-id is daemon
+(or that specified in the printcap file instead of daemon),
+and is intended for those instances where print filters wish to requeue jobs.
+.It Fl Z Ar daemon-options
+Some spoolers, such as
+.Tn LPRng ,
+accept additional per-job options using a
+.Ql Z
+control line.
+When
+.Fl Z
+is specified, and
+.Fl p
+.Pq Xr pr 1
+is not requested, the specified
+.Ar daemon-options
+will be passed to the remote
+.Tn LPRng
+spooler.
+.It Fl i Ar numcols
+The output is indented by
+.Pq Ar numcols .
+.It Fl w Ar num
+Uses
+.Ar num
+as the page width for
+.Xr pr 1 .
+.El
+.Sh ENVIRONMENT
+If the following environment variable exists, it is used by
+.Nm :
+.Bl -tag -width PRINTER
+.It Ev PRINTER
+Specifies an alternate default printer.
+.El
+.Sh FILES
+.Bl -tag -width /var/spool/output/*/tf* -compact
+.It Pa /etc/passwd
+Personal identification.
+.It Pa /etc/printcap
+Printer capabilities data base.
+.It Pa /usr/sbin/lpd
+Line printer daemons.
+.It Pa /var/spool/output/*
+Directories used for spooling.
+.It Pa /var/spool/output/*/cf*
+Daemon control files.
+.It Pa /var/spool/output/*/df*
+Data files specified in "cf" files.
+.It Pa /var/spool/output/*/tf*
+Temporary copies of "cf" files.
+.El
+.Sh DIAGNOSTICS
+If you try to spool too large a file, it will be truncated.
+The
+.Nm
+utility will object to printing binary files.
+If a user other than root prints a file and spooling is disabled,
+.Nm
+will print a message saying so and will not put jobs in the queue.
+If a connection to
+.Xr lpd 8
+on the local machine cannot be made,
+.Nm
+will say that the daemon cannot be started.
+Diagnostics may be printed in the daemon's log file
+regarding missing spool files by
+.Xr lpd 8 .
+.Sh SEE ALSO
+.Xr lpq 1 ,
+.Xr lprm 1 ,
+.Xr pr 1 ,
+.Xr symlink 2 ,
+.Xr printcap 5 ,
+.Xr lpc 8 ,
+.Xr lpd 8
+.Sh HISTORY
+The
+.Nm
+command appeared in
+.Bx 3 .
+.Sh BUGS
+Fonts for
+.Xr troff 1 Pq Pa ports/textproc/groff
+and
+.Tn TeX
+reside on the host with the printer.
+It is currently not possible to
+use local font libraries.
+.Pp
+The
+.Ql Z
+control file line is used for two different purposes; for
+standard
+.Fx
+.Xr lpd 8 ,
+it specifies a locale to be passed to
+.Xr pr 1 .
+For
+.Tn LPRng
+.Xr lpd 8 ,
+it specifies additional options to be interpreted by the spooler's
+input and output filters.
+When submitting jobs via
+.Nm ,
+.Fl p
+.Fl L Ar locale
+is used in the former context, and
+.Fl Z Ar daemon-options
+is used in the latter.
diff --git a/usr.sbin/lpr/lpr/lpr.c b/usr.sbin/lpr/lpr/lpr.c
new file mode 100644
index 000000000000..7d71bb2fa45c
--- /dev/null
+++ b/usr.sbin/lpr/lpr/lpr.c
@@ -0,0 +1,865 @@
+/*-
+ * SPDX-License-Identifier: BSD-4-Clause
+ *
+ * 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. All advertising materials mentioning features or use of this software
+ * must display the following acknowledgement:
+ * This product includes software developed by the University of
+ * California, Berkeley and its contributors.
+ * 4. 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 "lp.cdefs.h" /* A cross-platform version of <sys/cdefs.h> */
+/*
+ * 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 <netinet/in.h> /* N_BADMAG uses ntohl() */
+
+#include <dirent.h>
+#include <fcntl.h>
+#include <err.h>
+#include <locale.h>
+#include <signal.h>
+#include <syslog.h>
+#include <pwd.h>
+#include <grp.h>
+#include <unistd.h>
+#include <stdlib.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <ctype.h>
+#include <string.h>
+#include "lp.h"
+#include "lp.local.h"
+#include "pathnames.h"
+
+static char *cfname; /* daemon control files, linked from tf's */
+static char *class = local_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 char *lpr_username; /* person sending the print job(s) */
+static int qflag; /* q job, but don't exec daemon */
+static int rflag; /* remove files upon completion */
+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 char *locale; /* pr'ing locale */
+static int userid; /* user id */
+static char *Uflag; /* user name specified with -U flag */
+static char *width; /* width for versatec printing */
+static char *Zflag; /* extra filter options for LPRng servers */
+
+static struct stat statb;
+
+static void card(int _c, const char *_p2);
+static int checkwriteperm(const char *_file, const char *_directory);
+static void chkprinter(const char *_ptrname, struct printer *_pp);
+static void cleanup(int _signo);
+static void copy(const struct printer *_pp, int _f, const char _n[]);
+static char *itoa(int _i);
+static const char *linked(const char *_file);
+int main(int _argc, char *_argv[]);
+static char *lmktemp(const struct printer *_pp, const char *_id,
+ int _num, int len);
+static void mktemps(const struct printer *_pp);
+static int nfile(char *_n);
+static int test(const char *_file);
+static void usage(void);
+
+uid_t uid, euid;
+
+int
+main(int argc, char *argv[])
+{
+ struct passwd *pw;
+ struct group *gptr;
+ const char *arg, *cp, *printer;
+ char *p;
+ char buf[BUFSIZ];
+ int c, i, f, errs;
+ int ret, didlink;
+ struct stat stb;
+ struct stat statb1, statb2;
+ struct printer myprinter, *pp = &myprinter;
+
+ printer = NULL;
+ euid = geteuid();
+ uid = getuid();
+ PRIV_END
+ 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);
+
+ progname = argv[0];
+ gethostname(local_host, sizeof(local_host));
+ openlog("lpd", 0, LOG_LPR);
+
+ errs = 0;
+ while ((c = getopt(argc, argv,
+ ":#:1:2:3:4:C:J:L:P:T:U:Z:cdfghi:lnmprstvw:"))
+ != -1)
+ switch (c) {
+ case '#': /* n copies */
+ i = strtol(optarg, &p, 10);
+ if (*p)
+ errx(1, "Bad argument to -#, number expected");
+ if (i > 0)
+ ncopies = i;
+ break;
+
+ case '1': /* troff fonts */
+ case '2':
+ case '3':
+ case '4':
+ 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 'L': /* pr's locale */
+ locale = optarg;
+ break;
+
+ case 'T': /* pr's title line */
+ title = optarg;
+ break;
+
+ case 'U': /* user name */
+ hdr++;
+ Uflag = optarg;
+ break;
+
+ case 'Z':
+ Zflag = 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 'n': /* print ditroff output */
+ case 't': /* print troff output (cat files) */
+ case 'p': /* print using ``pr'' */
+ case 'v': /* print vplot output */
+ format = optopt;
+ break;
+
+ case 'f': /* print fortran output */
+ format = 'r';
+ break;
+
+ case 'h': /* nulifiy header page */
+ hdr = 0;
+ break;
+
+ case 'i': /* indent output */
+ iflag++;
+ indent = strtol(optarg, &p, 10);
+ if (*p)
+ errx(1, "Bad argument to -i, number expected");
+ break;
+
+ case 'm': /* send mail when done */
+ mailflg++;
+ break;
+
+ case 'q': /* just queue 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, pp);
+ if (pp->no_copies && ncopies > 1)
+ errx(1, "multiple copies are not allowed");
+ if (pp->max_copies > 0 && ncopies > pp->max_copies)
+ errx(1, "only %ld copies are allowed", pp->max_copies);
+ /*
+ * Get the identity of the person doing the lpr using the same
+ * algorithm as lprm. Actually, not quite -- lprm will override
+ * the login name with "root" if the user is running as root;
+ * the daemon actually checks for the string "root" in its
+ * permission checking. Sigh.
+ */
+ userid = getuid();
+ if (Uflag) {
+ if (userid != 0 && userid != pp->daemon_user)
+ errx(1, "only privileged users may use the `-U' flag");
+ lpr_username = Uflag; /* -U person doing 'lpr' */
+ } else {
+ lpr_username = getlogin(); /* person doing 'lpr' */
+ if (userid != pp->daemon_user || lpr_username == 0) {
+ if ((pw = getpwuid(userid)) == NULL)
+ errx(1, "Who are you?");
+ lpr_username = pw->pw_name;
+ }
+ }
+
+ /*
+ * Check for restricted group access.
+ */
+ if (pp->restrict_grp != NULL && userid != pp->daemon_user) {
+ if ((gptr = getgrnam(pp->restrict_grp)) == NULL)
+ errx(1, "Restricted group specified incorrectly");
+ if (gptr->gr_gid != getgid()) {
+ while (*gptr->gr_mem != NULL) {
+ if ((strcmp(lpr_username, *gptr->gr_mem)) == 0)
+ break;
+ gptr->gr_mem++;
+ }
+ if (*gptr->gr_mem == NULL)
+ errx(1, "Not a member of the restricted group");
+ }
+ }
+ /*
+ * Check to make sure queuing is enabled if userid is not root.
+ */
+ lock_file_name(pp, buf, sizeof buf);
+ if (userid && stat(buf, &stb) == 0 && (stb.st_mode & LFM_QUEUE_DIS))
+ errx(1, "Printer queue is disabled");
+ /*
+ * Initialize the control file.
+ */
+ mktemps(pp);
+ tfd = nfile(tfname);
+ PRIV_START
+ (void) fchown(tfd, pp->daemon_user, -1);
+ /* owned by daemon for protection */
+ PRIV_END
+ card('H', local_host);
+ card('P', lpr_username);
+ card('C', class);
+ if (hdr && !pp->no_header) {
+ if (jobname == NULL) {
+ if (argc == 0)
+ jobname = "stdin";
+ else
+ jobname = ((arg = strrchr(argv[0], '/'))
+ ? arg + 1 : argv[0]);
+ }
+ card('J', jobname);
+ card('L', lpr_username);
+ }
+ if (format != 'p' && Zflag != 0)
+ card('Z', Zflag);
+ if (iflag)
+ card('I', itoa(indent));
+ if (mailflg)
+ card('M', lpr_username);
+ 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);
+ /*
+ * XXX
+ * Our use of `Z' here is incompatible with LPRng's
+ * use. We assume that the only use of our existing
+ * `Z' card is as shown for `p' format (pr) files.
+ */
+ if (format == 'p') {
+ char *s;
+
+ if (locale)
+ card('Z', locale);
+ else if ((s = setlocale(LC_TIME, "")) != NULL)
+ card('Z', s);
+ }
+
+ /*
+ * Read the files and spool them.
+ */
+ if (argc == 0)
+ copy(pp, 0, " ");
+ else while (argc--) {
+ if (argv[0][0] == '-' && argv[0][1] == '\0') {
+ /* use stdin */
+ copy(pp, 0, " ");
+ argv++;
+ continue;
+ }
+ if ((f = test(arg = *argv++)) < 0)
+ continue; /* file unreasonable */
+
+ if (sflag && (cp = linked(arg)) != NULL) {
+ (void)snprintf(buf, sizeof(buf), "%ju %ju",
+ (uintmax_t)statb.st_dev, (uintmax_t)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)
+ printf("%s: %s: not linked, copying instead\n",
+ progname, arg);
+
+ if (f) {
+ /*
+ * The user wants the file removed after it is copied
+ * to the spool area, so see if the file can be moved
+ * instead of copy/unlink'ed. This is much faster and
+ * uses less spool space than copying the file. This
+ * can be very significant when running services like
+ * samba, pcnfs, CAP, et al.
+ */
+ PRIV_START
+ didlink = 0;
+ /*
+ * There are several things to check to avoid any
+ * security issues. Some of these are redundant
+ * under BSD's, but are necessary when lpr is built
+ * under some other OS's (which I do do...)
+ */
+ if (lstat(arg, &statb1) < 0)
+ goto nohardlink;
+ if (S_ISLNK(statb1.st_mode))
+ goto nohardlink;
+ if (link(arg, dfname) != 0)
+ goto nohardlink;
+ didlink = 1;
+ /*
+ * Make sure the user hasn't tried to trick us via
+ * any race conditions
+ */
+ if (lstat(dfname, &statb2) < 0)
+ goto nohardlink;
+ if (statb1.st_dev != statb2.st_dev)
+ goto nohardlink;
+ if (statb1.st_ino != statb2.st_ino)
+ goto nohardlink;
+ /*
+ * Skip if the file already had multiple hard links,
+ * because changing the owner and access-bits would
+ * change ALL versions of the file
+ */
+ if (statb2.st_nlink > 2)
+ goto nohardlink;
+ /*
+ * If we can access and remove the original file
+ * without special setuid-ness then this method is
+ * safe. Otherwise, abandon the move and fall back
+ * to the (usual) copy method.
+ */
+ PRIV_END
+ ret = access(dfname, R_OK);
+ if (ret == 0)
+ ret = unlink(arg);
+ PRIV_START
+ if (ret != 0)
+ goto nohardlink;
+ /*
+ * Unlink of user file was successful. Change the
+ * owner and permissions, add entries to the control
+ * file, and skip the file copying step.
+ */
+ chown(dfname, pp->daemon_user, getegid());
+ chmod(dfname, S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP);
+ PRIV_END
+ if (format == 'p')
+ card('T', title ? title : arg);
+ for (i = 0; i < ncopies; i++)
+ card(format, &dfname[inchar-2]);
+ card('U', &dfname[inchar-2]);
+ card('N', arg);
+ nact++;
+ continue;
+ nohardlink:
+ if (didlink)
+ unlink(dfname);
+ PRIV_END /* restore old uid */
+ } /* end: if (f) */
+
+ if ((i = open(arg, O_RDONLY)) < 0) {
+ printf("%s: cannot open %s\n", progname, arg);
+ } else {
+ copy(pp, i, arg);
+ (void) close(i);
+ if (f && unlink(arg) < 0)
+ printf("%s: %s: not removed\n", progname, arg);
+ }
+ }
+
+ if (nact) {
+ (void) close(tfd);
+ tfname[inchar]--;
+ /*
+ * Touch the control file to fix position in the queue.
+ */
+ PRIV_START
+ if ((tfd = open(tfname, O_RDWR)) >= 0) {
+ char touch_c;
+
+ if (read(tfd, &touch_c, 1) == 1 &&
+ lseek(tfd, (off_t)0, 0) == 0 &&
+ write(tfd, &touch_c, 1) != 1) {
+ printf("%s: cannot touch %s\n", progname,
+ tfname);
+ tfname[inchar]++;
+ cleanup(0);
+ }
+ (void) close(tfd);
+ }
+ if (link(tfname, cfname) < 0) {
+ printf("%s: cannot rename %s\n", progname, cfname);
+ tfname[inchar]++;
+ cleanup(0);
+ }
+ unlink(tfname);
+ PRIV_END
+ if (qflag) /* just q things up */
+ exit(0);
+ if (!startdaemon(pp))
+ printf("jobs queued, but cannot start daemon.\n");
+ exit(0);
+ }
+ cleanup(0);
+ return (1);
+ /* NOTREACHED */
+}
+
+/*
+ * Create the file n and copy from file descriptor f.
+ */
+static void
+copy(const struct printer *pp, int f, const char n[])
+{
+ register int fd, i, nr, 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, BUFSIZ)) > 0) {
+ if (write(fd, buf, i) != i) {
+ printf("%s: %s: temp file write error\n", progname, n);
+ break;
+ }
+ nc += i;
+ if (nc >= BUFSIZ) {
+ nc -= BUFSIZ;
+ nr++;
+ if (pp->max_blocks > 0 && nr > pp->max_blocks) {
+ printf("%s: %s: copy file is too large\n",
+ progname, n);
+ break;
+ }
+ }
+ }
+ (void) close(fd);
+ if (nc==0 && nr==0)
+ printf("%s: %s: empty input file\n", progname,
+ 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)
+{
+ register char *cp;
+ static char buf[MAXPATHLEN];
+ register int ret;
+
+ if (*file != '/') {
+ if (getcwd(buf, sizeof(buf)) == 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;
+ }
+ strncat(buf, "/", sizeof(buf) - strlen(buf) - 1);
+ strncat(buf, file, sizeof(buf) - strlen(buf) - 1);
+ file = buf;
+ }
+ PRIV_START
+ ret = symlink(file, dfname);
+ PRIV_END
+ return(ret ? NULL : file);
+}
+
+/*
+ * Put a line into the control file.
+ */
+static void
+card(int c, const char *p2)
+{
+ char buf[BUFSIZ];
+ register char *p1 = buf;
+ size_t len = 2;
+
+ *p1++ = c;
+ while ((c = *p2++) != '\0' && len < sizeof(buf)) {
+ *p1++ = (c == '\n') ? ' ' : c;
+ len++;
+ }
+ *p1++ = '\n';
+ write(tfd, buf, len);
+}
+
+/*
+ * Create a new file in the spool directory.
+ */
+static int
+nfile(char *n)
+{
+ register int f;
+ int oldumask = umask(0); /* should block signals */
+
+ PRIV_START
+ f = open(n, O_WRONLY | O_EXCL | O_CREAT, FILMOD);
+ (void) umask(oldumask);
+ if (f < 0) {
+ printf("%s: cannot create %s\n", progname, n);
+ cleanup(0);
+ }
+ if (fchown(f, userid, -1) < 0) {
+ printf("%s: cannot chown %s\n", progname, n);
+ cleanup(0); /* cleanup does exit */
+ }
+ PRIV_END
+ 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 __unused)
+{
+ register int i;
+
+ signal(SIGHUP, SIG_IGN);
+ signal(SIGINT, SIG_IGN);
+ signal(SIGQUIT, SIG_IGN);
+ signal(SIGTERM, SIG_IGN);
+ i = inchar;
+ PRIV_START
+ 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)
+{
+ size_t dlen;
+ int fd;
+ char *cp, *dirpath;
+
+ if (access(file, 4) < 0) {
+ printf("%s: cannot access %s\n", progname, file);
+ return(-1);
+ }
+ if (stat(file, &statb) < 0) {
+ printf("%s: cannot stat %s\n", progname, file);
+ return(-1);
+ }
+ if ((statb.st_mode & S_IFMT) == S_IFDIR) {
+ printf("%s: %s is a directory\n", progname, file);
+ return(-1);
+ }
+ if (statb.st_size == 0) {
+ printf("%s: %s is an empty file\n", progname, file);
+ return(-1);
+ }
+ if ((fd = open(file, O_RDONLY)) < 0) {
+ printf("%s: cannot open %s\n", progname, file);
+ return(-1);
+ }
+ (void) close(fd);
+ if (rflag) {
+ /*
+ * aside: note that 'cp' is technically a 'const char *'
+ * (because it points into 'file'), even though strrchr
+ * returns a value of type 'char *'.
+ */
+ if ((cp = strrchr(file, '/')) == NULL) {
+ if (checkwriteperm(file,".") == 0)
+ return(1);
+ } else {
+ if (cp == file) {
+ fd = checkwriteperm(file,"/");
+ } else {
+ /* strlcpy will change the '/' to '\0' */
+ dlen = cp - file + 1;
+ dirpath = malloc(dlen);
+ strlcpy(dirpath, file, dlen);
+ fd = checkwriteperm(file, dirpath);
+ free(dirpath);
+ }
+ if (fd == 0)
+ return(1);
+ }
+ printf("%s: %s: is not removable by you\n", progname, file);
+ }
+ return(0);
+}
+
+static int
+checkwriteperm(const char *file, const char *directory)
+{
+ struct stat stats;
+ if (access(directory, W_OK) == 0) {
+ stat(directory, &stats);
+ if (stats.st_mode & S_ISVTX) {
+ stat(file, &stats);
+ if(stats.st_uid == userid) {
+ return(0);
+ }
+ } else return(0);
+ }
+ return(-1);
+}
+
+/*
+ * itoa - integer to string conversion
+ */
+static char *
+itoa(int i)
+{
+ static char b[10] = "########";
+ register 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 *ptrname, struct printer *pp)
+{
+ int status;
+
+ init_printer(pp);
+ status = getprintcap(ptrname, pp);
+ switch(status) {
+ case PCAPERR_OSERR:
+ case PCAPERR_TCLOOP:
+ errx(1, "%s: %s", ptrname, pcaperr(status));
+ case PCAPERR_NOTFOUND:
+ errx(1, "%s: unknown printer", ptrname);
+ case PCAPERR_TCOPEN:
+ warnx("%s: unresolved tc= reference(s)", ptrname);
+ }
+}
+
+/*
+ * Tell the user what we wanna get.
+ */
+static void
+usage(void)
+{
+ fprintf(stderr, "%s\n",
+"usage: lpr [-Pprinter] [-#num] [-C class] [-J job] [-T title] [-U user]\n"
+ "\t[-Z daemon-options] [-i[numcols]] [-i[numcols]] [-1234 font]\n"
+ "\t[-L locale] [-wnum] [-cdfghlnmprstv] [name ...]");
+ exit(1);
+}
+
+
+/*
+ * Make the temp files.
+ */
+static void
+mktemps(const struct printer *pp)
+{
+ register int len, fd, n;
+ register char *cp;
+ char buf[BUFSIZ];
+
+ (void) snprintf(buf, sizeof(buf), "%s/.seq", pp->spool_dir);
+ PRIV_START
+ if ((fd = open(buf, O_RDWR|O_CREAT, 0664)) < 0) {
+ printf("%s: cannot create %s\n", progname, buf);
+ exit(1);
+ }
+ if (flock(fd, LOCK_EX)) {
+ printf("%s: cannot lock %s\n", progname, buf);
+ exit(1);
+ }
+ PRIV_END
+ 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');
+ }
+ }
+ len = strlen(pp->spool_dir) + strlen(local_host) + 8;
+ tfname = lmktemp(pp, "tf", n, len);
+ cfname = lmktemp(pp, "cf", n, len);
+ dfname = lmktemp(pp, "df", n, len);
+ inchar = strlen(pp->spool_dir) + 3;
+ n = (n + 1) % 1000;
+ (void) lseek(fd, (off_t)0, 0);
+ 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 struct printer *pp, const char *id, int num, int len)
+{
+ register char *s;
+
+ if ((s = malloc(len)) == NULL)
+ errx(1, "out of memory");
+ (void) snprintf(s, len, "%s/%sA%03d%s", pp->spool_dir, id, num,
+ local_host);
+ return(s);
+}
diff --git a/usr.sbin/lpr/lpr/printcap.5 b/usr.sbin/lpr/lpr/printcap.5
new file mode 100644
index 000000000000..e745fafc7b27
--- /dev/null
+++ b/usr.sbin/lpr/lpr/printcap.5
@@ -0,0 +1,431 @@
+.\" Copyright (c) 1983, 1991, 1993
+.\" The Regents of the University of California. All rights reserved.
+.\"
+.\" 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.
+.\"
+.Dd October 11, 2000
+.Dt PRINTCAP 5
+.Os
+.Sh NAME
+.Nm printcap
+.Nd printer capability data base
+.Sh SYNOPSIS
+.Nm
+.Sh DESCRIPTION
+The
+.Nm Printcap
+function
+is a simplified version of the
+.Xr termcap 5
+data base
+used to describe line printers.
+The spooling system accesses the
+.Nm
+file every time it is used, allowing dynamic
+addition and deletion of printers.
+Each entry in the data base
+is used to describe one printer.
+This data base may not be
+substituted for, as is possible for
+.Xr termcap 5 ,
+because it may allow accounting to be bypassed.
+.Pp
+The default printer is normally
+.Em lp ,
+though the environment variable
+.Ev PRINTER
+may be used to override this.
+Each spooling utility supports an option,
+.Fl P Ar printer ,
+to allow explicit naming of a destination printer.
+.Pp
+Refer to the
+.%T "4.3 BSD Line Printer Spooler Manual"
+for a complete discussion on how to setup the database for a given printer.
+.Sh CAPABILITIES
+Refer to
+.Xr termcap 5
+for a description of the file layout.
+.Bl -column Namexxx Typexx "/var/spool/lpdxxxxx"
+.Sy "Name Type Default Description"
+.It "af str" Ta Dv NULL Ta No "name of accounting file"
+.It "br num none if lp is a tty, set the baud rate"
+.Xr ( ioctl 2
+call)
+.It "cf str" Ta Dv NULL Ta No "cifplot data filter"
+.It "ct num 120 TCP connection timeout in seconds"
+.It "df str" Ta Dv NULL Ta No "tex data filter"
+.Tn ( DVI
+format)
+.It "du num 1 UID to run daemon as"
+.It "ff str" Ta So Li \ef Sc Ta No "string to send for a form feed"
+.It "fo bool false print a form feed when device is opened"
+.It "gf str" Ta Dv NULL Ta No "graph data filter"
+.Xr ( plot 3
+format
+.It "hl bool false print the burst header page last"
+.It "ic bool false driver supports (non standard) ioctl to indent printout"
+.It "if str" Ta Dv NULL Ta No "name of text filter which does accounting"
+.It "lf str" Ta Pa /dev/console Ta No "error logging file name"
+.It "lo str" Ta Pa lock Ta No "name of lock file"
+.It "lp str" Ta Pa /dev/lp Ta No "device name to open for output, or" Em port Ns @ Ns Em machine No "to open a TCP socket"
+.It "mc num 0 maximum number of copies which can be requested on"
+.Xr lpr 1 ,
+zero = unlimited
+.It "ms str" Ta Dv NULL Ta No "if lp is a tty, a comma-separated,"
+.Xr stty 1 Ns -like
+list describing the tty modes
+.It "mx num 0 maximum file size (in"
+.Dv BUFSIZ
+blocks), zero = unlimited
+.It "nd str" Ta Dv NULL Ta No "next directory for list of queues (unimplemented)"
+.It "nf str" Ta Dv NULL Ta No "ditroff data filter (device independent troff)"
+.It "of str" Ta Dv NULL Ta No "name of output filtering program"
+.It "pc num 200 price per foot or page in hundredths of cents"
+.It "pl num 66 page length (in lines)"
+.It "pw num 132 page width (in characters)"
+.It "px num 0 page width in pixels (horizontal)"
+.It "py num 0 page length in pixels (vertical)"
+.It "rc bool false when sending to a remote host, resend copies (see below)"
+.It "rf str" Ta Dv NULL Ta No "filter for printing"
+.Tn FORTRAN
+style text files
+.It "rg str" Ta Dv NULL Ta No "restricted group. Only members of group allowed access"
+.It "rm str" Ta Dv NULL Ta No "machine name for remote printer"
+.It "rp str" Ta Pa lp Ta No "remote printer name argument"
+.It "rs bool false restrict remote users to those with local accounts"
+.It "rw bool false open the printer device for reading and writing"
+.It "sb bool false short banner (one line only)"
+.It "sc bool false suppress multiple copies"
+.It "sd str" Ta Pa /var/spool/lpd Ta No "spool directory"
+.It "sf bool false suppress form feeds"
+.It "sh bool false suppress printing of burst page header"
+.It "sr str" Ta Dv NULL Ta No "file name to hold statistics of each datafile as it is received"
+.It "ss str" Ta Dv NULL Ta No "file name to hold statistics of each datafile as it is sent"
+.It "st str" Ta Pa status Ta No "status file name"
+.It "tf str" Ta Dv NULL Ta No "troff data filter (cat phototypesetter)"
+.It "tr str" Ta Dv NULL Ta No "trailer string to print when queue empties"
+.It "vf str" Ta Dv NULL Ta No "raster image filter"
+.El
+.Pp
+Each two-letter capability has a human-readable alternate name.
+.Bl -column "Short form" "Long form"
+.Sy "Short form Long form"
+.It "af acct.file"
+.It "br tty.rate"
+.It "cf filt.cifplot"
+.It "ct remote.timeout"
+.It "df filt.dvi"
+.It "du daemon.user"
+.It "ff job.formfeed"
+.It "fo job.topofform"
+.It "gf filt.plot"
+.It "hl banner.last"
+.It "if filt.input"
+.It "lf spool.log"
+.It "lo spool.lock"
+.It "lp tty.device"
+.It "mc max.copies"
+.It "ms tty.mode"
+.It "mx max.blocks"
+.It "nf filt.ditroff"
+.It "of filt.output"
+.It "pc acct.price"
+.It "pl page.length"
+.It "pw page.width"
+.It "px page.pwidth"
+.It "py page.plength"
+.It "rc remote.resend_copies"
+.It "rf filt.fortran"
+.It "rg daemon.restrictgrp"
+.It "rm remote.host"
+.It "rp remote.queue"
+.It "rs daemon.restricted"
+.It "rw tty.rw"
+.It "sb banner.short"
+.It "sc job.no_copies"
+.It "sd spool.dir"
+.It "sf job.no_formfeed"
+.It "sh banner.disable"
+.It "sr stat.recv"
+.It "ss stat.send"
+.It "st spool.status"
+.It "tf filt.troff"
+.It "tr job.trailer"
+.It "vf filt.raster"
+.El
+.Pp
+If the local line printer driver supports indentation, the daemon
+must understand how to invoke it.
+.Sh FILTERS
+The
+.Xr lpd 8
+daemon creates a pipeline of
+.Em filters
+to process files for various printer types.
+The filters selected depend on the flags passed to
+.Xr lpr 1 .
+The pipeline set up is:
+.Bd -literal -offset indent
+p pr | if regular text + pr(1)
+none if regular text
+c cf cifplot
+d df DVI (tex)
+g gf plot(3)
+n nf ditroff
+f rf Fortran
+t tf troff
+v vf raster image
+.Ed
+.Pp
+The
+.Sy if
+filter is invoked with arguments:
+.Bd -ragged -offset indent
+.Cm if
+.Op Fl c
+.Fl w Ns Ar width
+.Fl l Ns Ar length
+.Fl i Ns Ar indent
+.Fl n Ar login
+.Fl h Ar host acct-file
+.Ed
+.Pp
+The
+.Fl c
+flag is passed only if the
+.Fl l
+flag (pass control characters literally)
+is specified to
+.Xr lpr 1 .
+The
+.Ar Width
+function
+and
+.Ar length
+specify the page width and length
+(from
+.Cm pw
+and
+.Cm pl
+respectively) in characters.
+The
+.Fl n
+and
+.Fl h
+parameters specify the login name and host name of the owner
+of the job respectively.
+The
+.Ar Acct-file
+function
+is passed from the
+.Cm af
+.Nm
+entry.
+.Pp
+If no
+.Cm if
+is specified,
+.Cm of
+is used instead,
+with the distinction that
+.Cm of
+is opened only once,
+while
+.Cm if
+is opened for every individual job.
+Thus,
+.Cm if
+is better suited to performing accounting.
+The
+.Cm of
+is only given the
+.Ar width
+and
+.Ar length
+flags.
+.Pp
+All other filters are called as:
+.Bd -ragged -offset indent
+.Nm filter
+.Fl x Ns Ar width
+.Fl y Ns Ar length
+.Fl n Ar login
+.Fl h Ar host acct-file
+.Ed
+.Pp
+where
+.Ar width
+and
+.Ar length
+are represented in pixels,
+specified by the
+.Cm px
+and
+.Cm py
+entries respectively.
+.Pp
+All filters take
+.Em stdin
+as the file,
+.Em stdout
+as the printer,
+may log either to
+.Em stderr
+or using
+.Xr syslog 3 ,
+and must not ignore
+.Dv SIGINT .
+.Sh REMOTE PRINTING
+When printing to a remote printer using
+.Cm rm ,
+it is possible to use either
+.Cm if
+or
+.Cm of .
+If both are specified,
+.Cm of
+is ignored.
+Both filters behave the same except that they are passed
+different arguments as above.
+Specifically, the output filter is
+terminated and restarted for each file transmitted.
+This is necessary
+in order to pass the resulting size to the remote
+.Xr lpd 8 .
+.Pp
+If the
+.Fl p
+flag was passed to
+.Xr lpr 1 ,
+.Xr pr 1
+is not executed locally, but is requested of the remote
+.Xr lpd 8 .
+Any input filtering via
+.Cm if
+will therefore happen before
+.Xr pr 1
+is executed rather than afterwards.
+.Pp
+There are some models of network printers which accept jobs from
+.Xr lpd 8 ,
+but they ignore the control file for a job and simply print
+each data file as it arrives at the printer.
+One side-effect of this behavior is that the printer will ignore any request
+for multiple copies as given with the
+.Fl #
+flag on the
+.Xr lpr 1
+command.
+The
+.Cm rc
+entry will cause
+.Xr lpd 8
+to resend each data file for each copy that the user
+originally requested.
+Note that the
+.Cm rc
+entry should only be specified on hosts which send jobs directly to
+the printer.
+.Pp
+If
+.Cm lp
+is specified as
+.Em port Ns @ Ns Em machine
+(and
+.Cm rm
+is not in use), print data will be sent directly to the given
+.Em port
+on the given
+.Em machine .
+.Sh TRANSFER STATISTICS
+When a print job is transferred to a remote machine (which might be
+another unix box, or may be a network printer), it may be useful
+to keep statistics on each transfer.
+The
+.Cm sr
+and
+.Cm ss
+options indicate filenames that lpd should use to store such
+statistics.
+A statistics line is written for each datafile of a
+job as the file is successfully transferred.
+The format of the
+line is the same for both the sending and receiving side of a
+transfer.
+.Pp
+Statistics on datafiles being received would be used on a print
+server, if you are interested in network performance between a
+variety of machines which are sending jobs to that print server.
+The print server could collect statistics on the speed of each
+print job as it arrived on the server.
+.Pp
+Statistics on datafiles being sent might be used as a minimal
+accounting record, when you want to know who sent which jobs to a
+remote printer, when they were sent, and how large (in bytes) the
+files were.
+This will not give include any idea of how many pages
+were printed, because there is no standard way to get that information
+back from a remote (network) printer in this case.
+.Sh LOGGING
+Error messages generated by the line printer programs themselves
+(that is, the
+.Xr lpd 8
+and related programs)
+are logged by
+.Xr syslog 3
+using the
+.Dv LPR
+facility.
+Messages printed on
+.Em stderr
+of one of the filters
+are sent to the corresponding
+.Cm lf
+file.
+The filters may, of course, use
+.Xr syslogd 8
+themselves.
+.Pp
+Error messages sent to the console have a carriage return and a line
+feed appended to them, rather than just a line feed.
+.Sh SEE ALSO
+.Xr lpq 1 ,
+.Xr lpr 1 ,
+.Xr lprm 1 ,
+.Xr hosts.lpd 5 ,
+.Xr termcap 5 ,
+.Xr chkprintcap 8 ,
+.Xr lpc 8 ,
+.Xr lpd 8 ,
+.Xr pac 8
+.Rs
+.%T "4.3 BSD Line Printer Spooler Manual"
+.Re
+.Sh HISTORY
+The
+.Nm
+file format appeared in
+.Bx 4.2 .
diff --git a/usr.sbin/lpr/lprm/Makefile b/usr.sbin/lpr/lprm/Makefile
new file mode 100644
index 000000000000..49682eb88700
--- /dev/null
+++ b/usr.sbin/lpr/lprm/Makefile
@@ -0,0 +1,15 @@
+.PATH: ${.CURDIR:H}/common_source
+
+BINDIR= /usr/bin
+
+PACKAGE=lp
+PROG= lprm
+BINOWN= root
+BINGRP= daemon
+BINMODE= 6555
+
+CFLAGS+= -I${.CURDIR:H}/common_source
+
+LIBADD= lpr
+
+.include <bsd.prog.mk>
diff --git a/usr.sbin/lpr/lprm/Makefile.depend b/usr.sbin/lpr/lprm/Makefile.depend
new file mode 100644
index 000000000000..52597b984452
--- /dev/null
+++ b/usr.sbin/lpr/lprm/Makefile.depend
@@ -0,0 +1,16 @@
+# Autogenerated - do NOT edit!
+
+DIRDEPS = \
+ include \
+ include/xlocale \
+ lib/${CSU_DIR} \
+ lib/libc \
+ lib/libcompiler_rt \
+ usr.sbin/lpr/common_source \
+
+
+.include <dirdeps.mk>
+
+.if ${DEP_RELDIR} == ${_DEP_RELDIR}
+# local dependencies - needed for -jN in clean tree
+.endif
diff --git a/usr.sbin/lpr/lprm/lprm.1 b/usr.sbin/lpr/lprm/lprm.1
new file mode 100644
index 000000000000..8d35a3434969
--- /dev/null
+++ b/usr.sbin/lpr/lprm/lprm.1
@@ -0,0 +1,144 @@
+.\" Copyright (c) 1983, 1990, 1993
+.\" The Regents of the University of California. All rights reserved.
+.\"
+.\" 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.
+.\"
+.Dd June 6, 1993
+.Dt LPRM 1
+.Os
+.Sh NAME
+.Nm lprm
+.Nd remove jobs from the line printer spooling queue
+.Sh SYNOPSIS
+.Nm
+.Op Fl P Ns Ar printer
+.Op Fl
+.Op job # ...\&
+.Op Ar user ...\&
+.Sh DESCRIPTION
+The
+.Nm
+utility will remove a job, or jobs, from a printer's spool queue.
+Since the spooling directory is protected from users, using
+.Nm
+is normally the only method by which a user may remove a job.
+The owner of a job is determined by the user's login name
+and host name on the machine where the
+.Xr lpr 1
+command was invoked.
+.Pp
+Options and arguments:
+.Bl -tag -width indent
+.It Fl P Ns Ar printer
+Specify the queue associated with a specific
+.Ar printer
+(otherwise the default printer is used).
+.It Fl
+If a single
+.Sq Fl
+is given,
+.Nm
+will remove all jobs which a user
+owns.
+If the super-user employs this flag, the spool queue will
+be emptied entirely.
+.It Ar user
+Cause
+.Nm
+to attempt to remove any jobs queued belonging to that user
+(or users).
+This form of invoking
+.Nm
+is useful only to the super-user.
+.It Ar job\ \&#
+A user may dequeue an individual job by specifying its job number.
+This number may be obtained from the
+.Xr lpq 1
+program, e.g.\&
+.Bd -literal -offset indent
+\&% lpq \-l
+
+1st:ken [job #013ucbarpa]
+ (standard input) 100 bytes
+% lprm 13
+.Ed
+.El
+.Pp
+If neither arguments or options are given,
+.Nm
+will delete the currently active job if it is
+owned by the user who invoked
+.Nm .
+.Pp
+The
+.Nm
+utility announces the names of any files it removes and is silent if
+there are no jobs in the queue which match the request list.
+.Pp
+The
+.Nm
+utility will kill off an active daemon, if necessary, before removing
+any spooling files.
+If a daemon is killed, a new one is
+automatically restarted upon completion of file removals.
+.Sh ENVIRONMENT
+If the following environment variable exists, it is utilized by
+.Nm .
+.Bl -tag -width PRINTER
+.It Ev PRINTER
+If the environment variable
+.Ev PRINTER
+exists,
+and a printer has not been specified with the
+.Fl P
+option,
+the default printer is assumed from
+.Ev PRINTER .
+.El
+.Sh FILES
+.Bl -tag -width /var/spool/*/lock/ -compact
+.It Pa /etc/printcap
+Printer characteristics file.
+.It Pa /var/spool/*
+Spooling directories.
+.It Pa /var/spool/*/lock
+Lock file used to obtain the pid of the current
+daemon and the job number of the currently active job.
+.El
+.Sh DIAGNOSTICS
+``Permission denied" if the user tries to remove files other than his
+own.
+.Sh SEE ALSO
+.Xr lpq 1 ,
+.Xr lpr 1 ,
+.Xr lpd 8
+.Sh HISTORY
+The
+.Nm
+command appeared in
+.Bx 3.0 .
+.Sh BUGS
+Since there are race conditions possible in the update of the lock file,
+the currently active job may be incorrectly identified.
diff --git a/usr.sbin/lpr/lprm/lprm.c b/usr.sbin/lpr/lprm/lprm.c
new file mode 100644
index 000000000000..fd7b5ccec365
--- /dev/null
+++ b/usr.sbin/lpr/lprm/lprm.c
@@ -0,0 +1,150 @@
+/*-
+ * SPDX-License-Identifier: BSD-3-Clause
+ *
+ * Copyright (c) 1983, 1993
+ * The Regents of the University of California. All rights reserved.
+ *
+ *
+ * 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 "lp.cdefs.h" /* A cross-platform version of <sys/cdefs.h> */
+/*
+ * lprm - remove the current user's spool entry
+ *
+ * lprm [-] [[job #] [user] ...]
+ *
+ * Using information in the lock file, lprm will kill the
+ * currently active daemon (if necessary), remove the associated files,
+ * and startup a new daemon. Priviledged users may remove anyone's spool
+ * entries, otherwise one can only remove their own.
+ */
+
+#include <sys/param.h>
+
+#include <syslog.h>
+#include <dirent.h>
+#include <err.h>
+#include <pwd.h>
+#include <unistd.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <string.h>
+#include <ctype.h>
+#include "lp.h"
+#include "lp.local.h"
+
+/*
+ * Stuff for handling job specifications
+ */
+char *person; /* name of person doing lprm */
+int requ[MAXREQUESTS]; /* job number of spool entries */
+int requests; /* # of spool requests */
+char *user[MAXUSERS]; /* users to process */
+int users; /* # of users in user array */
+uid_t uid, euid; /* real and effective user id's */
+
+static char luser[16]; /* buffer for person */
+
+int main(int argc, char *_argv[]);
+static void usage(void);
+
+int
+main(int argc, char *argv[])
+{
+ char *arg;
+ const char *printer;
+ struct passwd *p;
+ static char root[] = "root";
+
+ printer = NULL;
+ uid = getuid();
+ euid = geteuid();
+ PRIV_END /* be safe */
+ progname = argv[0];
+ gethostname(local_host, sizeof(local_host));
+ openlog("lpd", 0, LOG_LPR);
+
+ /*
+ * Bogus code later checks for string equality between
+ * `person' and "root", so if we are root, better make sure
+ * that code will succeed.
+ */
+ if (getuid() == 0) {
+ person = root;
+ } else if ((person = getlogin()) == NULL) {
+ if ((p = getpwuid(getuid())) == NULL)
+ fatal(0, "Who are you?");
+ if (strlen(p->pw_name) >= sizeof(luser))
+ fatal(0, "Your name is too long");
+ strcpy(luser, p->pw_name);
+ person = luser;
+ }
+ while (--argc) {
+ if ((arg = *++argv)[0] == '-')
+ switch (arg[1]) {
+ case 'P':
+ if (arg[2])
+ printer = &arg[2];
+ else if (argc > 1) {
+ argc--;
+ printer = *++argv;
+ }
+ break;
+ case '\0':
+ if (!users) {
+ users = -1;
+ break;
+ }
+ default:
+ usage();
+ }
+ else {
+ if (users < 0)
+ usage();
+ if (isdigit(arg[0])) {
+ if (requests >= MAXREQUESTS)
+ fatal(0, "Too many requests");
+ requ[requests++] = atoi(arg);
+ } else {
+ if (users >= MAXUSERS)
+ fatal(0, "Too many users");
+ user[users++] = arg;
+ }
+ }
+ }
+ if (printer == NULL && (printer = getenv("PRINTER")) == NULL)
+ printer = DEFLP;
+
+ rmjob(printer);
+ exit(0);
+}
+
+static void
+usage(void)
+{
+ fprintf(stderr, "usage: lprm [-] [-Pprinter] [[job #] [user] ...]\n");
+ exit(2);
+}
diff --git a/usr.sbin/lpr/lptest/Makefile b/usr.sbin/lpr/lptest/Makefile
new file mode 100644
index 000000000000..cc2243d46ec0
--- /dev/null
+++ b/usr.sbin/lpr/lptest/Makefile
@@ -0,0 +1,6 @@
+PACKAGE=lp
+PROG= lptest
+
+CFLAGS+= -I${.CURDIR:H}/common_source
+
+.include <bsd.prog.mk>
diff --git a/usr.sbin/lpr/lptest/Makefile.depend b/usr.sbin/lpr/lptest/Makefile.depend
new file mode 100644
index 000000000000..93249906da4f
--- /dev/null
+++ b/usr.sbin/lpr/lptest/Makefile.depend
@@ -0,0 +1,14 @@
+# Autogenerated - do NOT edit!
+
+DIRDEPS = \
+ include \
+ lib/${CSU_DIR} \
+ lib/libc \
+ lib/libcompiler_rt \
+
+
+.include <dirdeps.mk>
+
+.if ${DEP_RELDIR} == ${_DEP_RELDIR}
+# local dependencies - needed for -jN in clean tree
+.endif
diff --git a/usr.sbin/lpr/lptest/lptest.1 b/usr.sbin/lpr/lptest/lptest.1
new file mode 100644
index 000000000000..76f9c2418ce8
--- /dev/null
+++ b/usr.sbin/lpr/lptest/lptest.1
@@ -0,0 +1,70 @@
+.\" Copyright (c) 1985, 1990, 1993
+.\" The Regents of the University of California. All rights reserved.
+.\"
+.\" 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.
+.\"
+.Dd December 30, 1993
+.Dt LPTEST 1
+.Os
+.Sh NAME
+.Nm lptest
+.Nd generate lineprinter ripple pattern
+.Sh SYNOPSIS
+.Nm
+.Op Ar length
+.Op Ar count
+.Sh DESCRIPTION
+The
+.Nm
+utility writes the traditional "ripple test" pattern on standard output.
+In 96 lines,
+this pattern will print all 96 printable
+.Tn ASCII
+characters
+in each position.
+While originally created to test printers, it is quite
+useful for testing terminals,
+driving terminal ports for debugging purposes,
+or any other task where a quick supply of random data is needed.
+.Pp
+The
+.Ar length
+argument specifies the output line length if the default
+length of 79 is inappropriate.
+.Pp
+The
+.Ar count
+argument specifies the number of output lines to be generated if
+the default count of 200 is inappropriate.
+Note that if
+.Ar count
+is to be specified,
+.Ar length
+must be also be specified.
+.Sh HISTORY
+A
+.Nm
+utility appeared in
+.Bx 4.3 .
diff --git a/usr.sbin/lpr/lptest/lptest.c b/usr.sbin/lpr/lptest/lptest.c
new file mode 100644
index 000000000000..771652de4892
--- /dev/null
+++ b/usr.sbin/lpr/lptest/lptest.c
@@ -0,0 +1,73 @@
+/*-
+ * SPDX-License-Identifier: BSD-3-Clause
+ *
+ * Copyright (c) 1983, 1993
+ * The Regents of the University of California. All rights reserved.
+ *
+ *
+ * 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 "lp.cdefs.h" /* A cross-platform version of <sys/cdefs.h> */
+#include <stdlib.h>
+#include <stdio.h>
+#include <err.h>
+
+/*
+ * lptest -- line printer test program (and other devices).
+ */
+int
+main(int argc, char **argv)
+{
+ int len, count;
+ register int i, j, fc, nc;
+ char outbuf[BUFSIZ];
+
+ setbuf(stdout, outbuf);
+ if (argc >= 2)
+ len = atoi(argv[1]);
+ else
+ len = 79;
+ if (argc >= 3)
+ count = atoi(argv[2]);
+ else
+ count = 200;
+ fc = ' ';
+ for (i = 0; i < count; i++) {
+ if (++fc == 0177)
+ fc = ' ';
+ nc = fc;
+ for (j = 0; j < len; j++) {
+ if (putchar(nc) == EOF)
+ err(1, "Write error");
+ if (++nc == 0177)
+ nc = ' ';
+ }
+ if (putchar('\n') == EOF)
+ err(1, "Write error");
+ }
+ (void) fflush(stdout);
+ exit(0);
+}
diff --git a/usr.sbin/lpr/pac/Makefile b/usr.sbin/lpr/pac/Makefile
new file mode 100644
index 000000000000..96a3b3c87232
--- /dev/null
+++ b/usr.sbin/lpr/pac/Makefile
@@ -0,0 +1,11 @@
+.PATH: ${.CURDIR:H}/common_source
+
+PACKAGE=lp
+PROG= pac
+MAN= pac.8
+
+CFLAGS+= -I${.CURDIR:H}/common_source
+
+LIBADD= lpr
+
+.include <bsd.prog.mk>
diff --git a/usr.sbin/lpr/pac/Makefile.depend b/usr.sbin/lpr/pac/Makefile.depend
new file mode 100644
index 000000000000..52597b984452
--- /dev/null
+++ b/usr.sbin/lpr/pac/Makefile.depend
@@ -0,0 +1,16 @@
+# Autogenerated - do NOT edit!
+
+DIRDEPS = \
+ include \
+ include/xlocale \
+ lib/${CSU_DIR} \
+ lib/libc \
+ lib/libcompiler_rt \
+ usr.sbin/lpr/common_source \
+
+
+.include <dirdeps.mk>
+
+.if ${DEP_RELDIR} == ${_DEP_RELDIR}
+# local dependencies - needed for -jN in clean tree
+.endif
diff --git a/usr.sbin/lpr/pac/pac.8 b/usr.sbin/lpr/pac/pac.8
new file mode 100644
index 000000000000..145d54ad4562
--- /dev/null
+++ b/usr.sbin/lpr/pac/pac.8
@@ -0,0 +1,102 @@
+.\" Copyright (c) 1983, 1991, 1993
+.\" The Regents of the University of California. All rights reserved.
+.\"
+.\" 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.
+.\"
+.Dd June 6, 1993
+.Dt PAC 8
+.Os
+.Sh NAME
+.Nm pac
+.Nd printer/plotter accounting information
+.Sh SYNOPSIS
+.Nm
+.Op Fl P Ns Ar printer
+.Op Fl c
+.Op Fl m
+.Op Fl p Ns Ar price
+.Op Fl s
+.Op Fl r
+.Op Ar name ...
+.Sh DESCRIPTION
+The
+.Nm
+utility reads the printer/plotter accounting files, accumulating the number
+of pages (the usual case) or feet (for raster devices)
+of paper consumed by each user, and printing out
+how much each user consumed in pages or feet and dollars.
+.Pp
+Options and operands available:
+.Bl -tag -width PPprinter
+.It Fl P Ns Ar printer
+Accounting is done for the named printer.
+Normally, accounting is done for the default printer (site dependent) or
+the value of the environment variable
+.Ev PRINTER
+is used.
+.It Fl c
+Cause the output to be sorted by cost; usually the
+output is sorted alphabetically by name.
+.It Fl m
+Cause the host name to be ignored in the accounting file.
+This
+allows for a user on multiple machines to have all of his printing
+charges grouped together.
+.It Fl p Ns Ar price
+The value
+.Ar price
+is used for the cost in dollars instead of the default value of 0.02
+or the price specified in
+.Pa /etc/printcap .
+.It Fl r
+Reverse the sorting order.
+.It Fl s
+Accounting information is summarized on the
+summary accounting file; this summarization is necessary since on a
+busy system, the accounting file can grow by several lines per day.
+.It Ar names
+Statistics are only printed for user(s)
+.Ar name ;
+usually, statistics are printed for every user who has used any paper.
+.El
+.Sh FILES
+.Bl -tag -width /var/account/?_sum -compact
+.It Pa /var/account/?acct
+raw accounting files
+.It Pa /var/account/?_sum
+summary accounting files
+.It Pa /etc/printcap
+printer capability data base
+.El
+.Sh SEE ALSO
+.Xr printcap 5
+.Sh HISTORY
+The
+.Nm
+utility appeared in
+.Bx 4.0 .
+.Sh BUGS
+The relationship between the computed price and reality is
+as yet unknown.
diff --git a/usr.sbin/lpr/pac/pac.c b/usr.sbin/lpr/pac/pac.c
new file mode 100644
index 000000000000..260743f5c33a
--- /dev/null
+++ b/usr.sbin/lpr/pac/pac.c
@@ -0,0 +1,435 @@
+/*-
+ * SPDX-License-Identifier: BSD-3-Clause
+ *
+ * Copyright (c) 1983, 1993
+ * The Regents of the University of California. All rights reserved.
+ *
+ *
+ * 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 "lp.cdefs.h" /* A cross-platform version of <sys/cdefs.h> */
+/*
+ * Do Printer accounting summary.
+ * Currently, usage is
+ * pac [-Pprinter] [-pprice] [-s] [-r] [-c] [-m] [user ...]
+ * to print the usage information for the named people.
+ */
+
+#include <sys/param.h>
+
+#include <dirent.h>
+#include <err.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <string.h>
+#include <unistd.h>
+#include "lp.h"
+#include "lp.local.h"
+
+static char *acctfile; /* accounting file (input data) */
+static int allflag = 1; /* Get stats on everybody */
+static int errs;
+static size_t hcount; /* Count of hash entries */
+static int mflag = 0; /* disregard machine names */
+static int pflag = 0; /* 1 if -p on cmd line */
+static float price = 0.02; /* cost per page (or what ever) */
+static int reverse; /* Reverse sort order */
+static int sort; /* Sort by cost */
+static char *sumfile; /* summary file */
+static int summarize; /* Compress accounting file */
+
+uid_t uid, euid;
+
+/*
+ * Grossness follows:
+ * Names to be accumulated are hashed into the following
+ * table.
+ */
+
+#define HSHSIZE 97 /* Number of hash buckets */
+
+struct hent {
+ struct hent *h_link; /* Forward hash link */
+ char *h_name; /* Name of this user */
+ float h_feetpages; /* Feet or pages of paper */
+ int h_count; /* Number of runs */
+};
+
+static struct hent *hashtab[HSHSIZE]; /* Hash table proper */
+
+int main(int argc, char **_argv);
+static void account(FILE *_acctf);
+static int any(int _ch, const char _str[]);
+static int chkprinter(const char *_ptrname);
+static void dumpit(void);
+static int hash(const char _name[]);
+static struct hent *enter(const char _name[]);
+static struct hent *lookup(const char _name[]);
+static int qucmp(const void *_a, const void *_b);
+static void rewrite(void);
+static void usage(void);
+
+int
+main(int argc, char **argv)
+{
+ FILE *acctf;
+ const char *cp, *printer;
+
+ printer = NULL;
+ euid = geteuid(); /* these aren't used in pac(1) */
+ uid = getuid();
+ while (--argc) {
+ cp = *++argv;
+ if (*cp++ == '-') {
+ switch(*cp++) {
+ case 'P':
+ /*
+ * Printer name.
+ */
+ printer = cp;
+ continue;
+
+ case 'p':
+ /*
+ * get the price.
+ */
+ price = atof(cp);
+ pflag = 1;
+ continue;
+
+ case 's':
+ /*
+ * Summarize and compress accounting file.
+ */
+ summarize++;
+ continue;
+
+ case 'c':
+ /*
+ * Sort by cost.
+ */
+ sort++;
+ continue;
+
+ case 'm':
+ /*
+ * disregard machine names for each user
+ */
+ mflag = 1;
+ continue;
+
+ case 'r':
+ /*
+ * Reverse sorting order.
+ */
+ reverse++;
+ continue;
+
+ default:
+ usage();
+ }
+ }
+ (void) enter(--cp);
+ allflag = 0;
+ }
+ if (printer == NULL && (printer = getenv("PRINTER")) == NULL)
+ printer = DEFLP;
+ if (!chkprinter(printer)) {
+ printf("pac: unknown printer %s\n", printer);
+ exit(2);
+ }
+
+ if ((acctf = fopen(acctfile, "r")) == NULL) {
+ perror(acctfile);
+ exit(1);
+ }
+ account(acctf);
+ fclose(acctf);
+ if ((acctf = fopen(sumfile, "r")) != NULL) {
+ account(acctf);
+ fclose(acctf);
+ }
+ if (summarize)
+ rewrite();
+ else
+ dumpit();
+ exit(errs);
+}
+
+static void
+usage(void)
+{
+ fprintf(stderr,
+ "usage: pac [-Pprinter] [-pprice] [-s] [-c] [-r] [-m] [user ...]\n");
+ exit(1);
+}
+
+/*
+ * Read the entire accounting file, accumulating statistics
+ * for the users that we have in the hash table. If allflag
+ * is set, then just gather the facts on everyone.
+ * Note that we must accommodate both the active and summary file
+ * formats here.
+ * Host names are ignored if the -m flag is present.
+ */
+static void
+account(FILE *acctf)
+{
+ char linebuf[BUFSIZ];
+ double t;
+ register char *cp, *cp2;
+ register struct hent *hp;
+ register int ic;
+
+ while (fgets(linebuf, BUFSIZ, acctf) != NULL) {
+ cp = linebuf;
+ while (any(*cp, " \t"))
+ cp++;
+ t = atof(cp);
+ while (any(*cp, ".0123456789"))
+ cp++;
+ while (any(*cp, " \t"))
+ cp++;
+ for (cp2 = cp; !any(*cp2, " \t\n"); cp2++)
+ ;
+ ic = atoi(cp2);
+ *cp2 = '\0';
+ if (mflag && strchr(cp, ':'))
+ cp = strchr(cp, ':') + 1;
+ hp = lookup(cp);
+ if (hp == NULL) {
+ if (!allflag)
+ continue;
+ hp = enter(cp);
+ }
+ hp->h_feetpages += t;
+ if (ic)
+ hp->h_count += ic;
+ else
+ hp->h_count++;
+ }
+}
+
+/*
+ * Sort the hashed entries by name or footage
+ * and print it all out.
+ */
+static void
+dumpit(void)
+{
+ struct hent **base;
+ register struct hent *hp, **ap;
+ register int hno, runs;
+ size_t c;
+ float feet;
+
+ hp = hashtab[0];
+ hno = 1;
+ base = (struct hent **) calloc(hcount, sizeof(hp));
+ for (ap = base, c = hcount; c--; ap++) {
+ while (hp == NULL)
+ hp = hashtab[hno++];
+ *ap = hp;
+ hp = hp->h_link;
+ }
+ qsort(base, hcount, sizeof hp, qucmp);
+ printf(" Login pages/feet runs price\n");
+ feet = 0.0;
+ runs = 0;
+ for (ap = base, c = hcount; c--; ap++) {
+ hp = *ap;
+ runs += hp->h_count;
+ feet += hp->h_feetpages;
+ printf("%-24s %7.2f %4d $%6.2f\n", hp->h_name,
+ hp->h_feetpages, hp->h_count, hp->h_feetpages * price);
+ }
+ if (allflag) {
+ printf("\n");
+ printf("%-24s %7.2f %4d $%6.2f\n", "total", feet,
+ runs, feet * price);
+ }
+}
+
+/*
+ * Rewrite the summary file with the summary information we have accumulated.
+ */
+static void
+rewrite(void)
+{
+ register struct hent *hp;
+ register int i;
+ FILE *acctf;
+
+ if ((acctf = fopen(sumfile, "w")) == NULL) {
+ warn("%s", sumfile);
+ errs++;
+ return;
+ }
+ for (i = 0; i < HSHSIZE; i++) {
+ hp = hashtab[i];
+ while (hp != NULL) {
+ fprintf(acctf, "%7.2f\t%s\t%d\n", hp->h_feetpages,
+ hp->h_name, hp->h_count);
+ hp = hp->h_link;
+ }
+ }
+ fflush(acctf);
+ if (ferror(acctf)) {
+ warn("%s", sumfile);
+ errs++;
+ }
+ fclose(acctf);
+ if ((acctf = fopen(acctfile, "w")) == NULL)
+ warn("%s", acctfile);
+ else
+ fclose(acctf);
+}
+
+/*
+ * Hashing routines.
+ */
+
+/*
+ * Enter the name into the hash table and return the pointer allocated.
+ */
+
+static struct hent *
+enter(const char name[])
+{
+ register struct hent *hp;
+ register int h;
+
+ if ((hp = lookup(name)) != NULL)
+ return(hp);
+ h = hash(name);
+ hcount++;
+ hp = (struct hent *) calloc(1, sizeof(*hp));
+ hp->h_name = strdup(name);
+ hp->h_feetpages = 0.0;
+ hp->h_count = 0;
+ hp->h_link = hashtab[h];
+ hashtab[h] = hp;
+ return(hp);
+}
+
+/*
+ * Lookup a name in the hash table and return a pointer
+ * to it.
+ */
+
+static struct hent *
+lookup(const char name[])
+{
+ register int h;
+ register struct hent *hp;
+
+ h = hash(name);
+ for (hp = hashtab[h]; hp != NULL; hp = hp->h_link)
+ if (strcmp(hp->h_name, name) == 0)
+ return(hp);
+ return(NULL);
+}
+
+/*
+ * Hash the passed name and return the index in
+ * the hash table to begin the search.
+ */
+static int
+hash(const char name[])
+{
+ register int h;
+ register const char *cp;
+
+ for (cp = name, h = 0; *cp; h = (h << 2) + *cp++)
+ ;
+ return((h & 0x7fffffff) % HSHSIZE);
+}
+
+/*
+ * Other stuff
+ */
+static int
+any(int ch, const char str[])
+{
+ register int c = ch;
+ register const char *cp = str;
+
+ while (*cp)
+ if (*cp++ == c)
+ return(1);
+ return(0);
+}
+
+/*
+ * The qsort comparison routine.
+ * The comparison is ascii collating order
+ * or by feet of typesetter film, according to sort.
+ */
+static int
+qucmp(const void *a, const void *b)
+{
+ register const struct hent *h1, *h2;
+ register int r;
+
+ h1 = *(const struct hent * const *)a;
+ h2 = *(const struct hent * const *)b;
+ if (sort)
+ r = h1->h_feetpages < h2->h_feetpages ?
+ -1 : h1->h_feetpages > h2->h_feetpages;
+ else
+ r = strcmp(h1->h_name, h2->h_name);
+ return(reverse ? -r : r);
+}
+
+/*
+ * Perform lookup for printer name or abbreviation --
+ */
+static int
+chkprinter(const char *ptrname)
+{
+ int stat;
+ struct printer myprinter, *pp = &myprinter;
+
+ init_printer(&myprinter);
+ stat = getprintcap(ptrname, pp);
+ switch(stat) {
+ case PCAPERR_OSERR:
+ printf("pac: getprintcap: %s\n", pcaperr(stat));
+ exit(3);
+ case PCAPERR_NOTFOUND:
+ return 0;
+ case PCAPERR_TCLOOP:
+ fatal(pp, "%s", pcaperr(stat));
+ }
+ if ((acctfile = pp->acct_file) == NULL)
+ errx(3, "accounting not enabled for printer %s", ptrname);
+ if (!pflag && pp->price100)
+ price = pp->price100/10000.0;
+ asprintf(&sumfile, "%s_sum", acctfile);
+ if (sumfile == NULL)
+ errx(1, "asprintf failed");
+ return(1);
+}