diff options
Diffstat (limited to 'usr.sbin/lpr')
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); +} |
