Index: head/usr.sbin/lpr/chkprintcap/chkprintcap.c =================================================================== --- head/usr.sbin/lpr/chkprintcap/chkprintcap.c (revision 297794) +++ head/usr.sbin/lpr/chkprintcap/chkprintcap.c (revision 297795) @@ -1,318 +1,318 @@ /* * 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 */ __FBSDID("$FreeBSD$"); #include #include #include #include #include #include #include #include #include #include #include /* needed for lp.h but not used here */ #include /* 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->fatalerr) return (skres->fatalerr); /* * 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); } 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 == 0) + 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 = 0; + 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 != 0 && equal(dp, dp2)) { + 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 == 0) + 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); } Index: head/usr.sbin/lpr/common_source/common.c =================================================================== --- head/usr.sbin/lpr/common_source/common.c (revision 297794) +++ head/usr.sbin/lpr/common_source/common.c (revision 297795) @@ -1,778 +1,778 @@ /* * 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. */ #if 0 #ifndef lint static char sccsid[] = "@(#)common.c 8.5 (Berkeley) 4/28/95"; #endif /* not lint */ #endif #include "lp.cdefs.h" /* A cross-platform version of */ __FBSDID("$FreeBSD$"); #include #include #include #include #include #include #include #include #include #include #include #include #include #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)) /* * Getline 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 getline(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) { arraysz *= 2; queue = (struct jobqueue **)realloc((char *)queue, arraysz * sizeof(struct jobqueue *)); if (queue == NULL) goto errdone; } 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 == 0) + 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 == 0) + 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\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); return; } 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: * - time the transfer started * - name of the printer queue (the short-name...) * - hostname the file originally came from (the * 'lpr host'), if known, or "_na_" if not known. * - id of job from that host (generally three digits) * - file count (# of file within job) * - 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= - user who sent the job (if known) * secs= - seconds it took to transfer the file * bytes= - number of bytes transfered (ie, "bytecount") * bps=e - Bytes/sec (if the transfer was "big enough" * for this to be useful) * ! top= - type of printer (if the type is defined in * printcap, and if this statline is for sending * a file to that ptr) * ! qls= - queue-length at start of send/print-ing a job * ! qle= - queue-length at end of send/print-ing a job * sip= - IP address of sending host, only included when * receiving a job. * shost= - sending host (if that does != the original host) * rhost= - hostname receiving the file (ie, "destination") * rdev= - 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 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); } } Index: head/usr.sbin/lpr/common_source/net.c =================================================================== --- head/usr.sbin/lpr/common_source/net.c (revision 297794) +++ head/usr.sbin/lpr/common_source/net.c (revision 297795) @@ -1,298 +1,298 @@ /* * 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. * * From: @(#)common.c 8.5 (Berkeley) 4/28/95 */ #include "lp.cdefs.h" /* A cross-platform version of */ __FBSDID("$FreeBSD$"); #include #include #include #include #include #include #include #include #include /* required for lp.h, not used here */ #include #include #include #include #include #include #include #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 == 0) + 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; } Index: head/usr.sbin/lpr/common_source/printcap.c =================================================================== --- head/usr.sbin/lpr/common_source/printcap.c (revision 297794) +++ head/usr.sbin/lpr/common_source/printcap.c (revision 297795) @@ -1,451 +1,451 @@ /* * 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. */ #if 0 #ifndef lint static char sccsid[] = "@(#)printcap.c 8.2 (Berkeley) 4/28/95"; #endif /* not lint */ #endif #include "lp.cdefs.h" /* A cross-platform version of */ __FBSDID("$FreeBSD$"); #include #include #include #include #include #include /* required for lp.h, but not used here */ #include /* ditto */ #include "lp.h" #include "lp.local.h" #include "pathnames.h" /* * Routines and data used in processing the printcap file. */ static char *printcapdb[2] = { _PATH_PRINTCAP, 0 }; /* list for cget* */ 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 *XXX; char *bp; /* * A bug in the declaration of cgetent(3) means that we have * to hide the constness of its third argument. */ XXX = (char *)printer; if ((status = cgetent(&bp, printcapdb, XXX)) < 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)) == 0) + 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, (char *)/*XXX*/lng, result); if (status >= 0 || status == PCAPERR_OSERR) return status; status = cgetstr(bp, (char *)/*XXX*/shrt, result); if (status >= 0 || status == PCAPERR_OSERR) return status; if (dflt) { *result = strdup(dflt); - if (*result == 0) + 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, (char *)/*XXX*/lng, result); if (status >= 0) return status; status = cgetnum(bp, (char *)/*XXX*/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, (char *)/*XXX*/lng, ':')) return 1; if (cgetcap(bp, (char *)/*XXX*/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 == 0) + if (nameend == NULL) nameend = bp + 1; - if ((retval = malloc(nameend - bp + 1)) != 0) { + if ((retval = malloc(nameend - bp + 1)) != NULL) { retval[0] = '\0'; strncat(retval, bp, nameend - bp); } return retval; } Index: head/usr.sbin/lpr/common_source/request.c =================================================================== --- head/usr.sbin/lpr/common_source/request.c (revision 297794) +++ head/usr.sbin/lpr/common_source/request.c (revision 297795) @@ -1,81 +1,81 @@ /* * 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 */ __FBSDID("$FreeBSD$"); #include #include #include #include #include /* needed for lp.h but not used here */ #include /* ditto */ #include /* 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)) != 0) { + while ((ru = TAILQ_FIRST(&rp->users)) != NULL) { TAILQ_REMOVE(&rp->users, ru, ru_link); free(ru); } - while ((rj = TAILQ_FIRST(&rp->jobids)) != 0) { + while ((rj = TAILQ_FIRST(&rp->jobids)) != NULL) { TAILQ_REMOVE(&rp->jobids, rj, rj_link); free(rj); } init_request(rp); } Index: head/usr.sbin/lpr/common_source/rmjob.c =================================================================== --- head/usr.sbin/lpr/common_source/rmjob.c (revision 297794) +++ head/usr.sbin/lpr/common_source/rmjob.c (revision 297795) @@ -1,392 +1,392 @@ /* * 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. * 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. */ #if 0 #ifndef lint static char sccsid[] = "@(#)rmjob.c 8.2 (Berkeley) 4/28/95"; #endif /* not lint */ #endif #include "lp.cdefs.h" /* A cross-platform version of */ __FBSDID("$FreeBSD$"); #include #include #include #include #include #include #include #include #include #include #define psignal foil_gcc_psignal #define sys_siglist foil_gcc_siglist #include #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 (!getline(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 (getline(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 (getline(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 == 0) + 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) */ } Index: head/usr.sbin/lpr/lpc/lpc.c =================================================================== --- head/usr.sbin/lpr/lpc/lpc.c (revision 297794) +++ head/usr.sbin/lpr/lpc/lpc.c (revision 297795) @@ -1,419 +1,419 @@ /* * 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. * 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. */ #ifndef lint static const char copyright[] = "@(#) Copyright (c) 1983, 1993\n\ The Regents of the University of California. All rights reserved.\n"; #endif /* not lint */ #if 0 #ifndef lint static char sccsid[] = "@(#)lpc.c 8.3 (Berkeley) 4/28/95"; #endif /* not lint */ #endif #include "lp.cdefs.h" /* A cross-platform version of */ __FBSDID("$FreeBSD$"); #include #include #include #include #include #include #include #include #include #include #include #include #include #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 == 0) { + 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 != 0) + 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 = (num > MAX_CMDLINE - 1) ? 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, margv) != -1) continue; c = getcmd(margv[0]); if (c == (struct cmd *)-1) { printf("?Ambiguous command\n"); continue; } - if (c == 0) { + 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 != 0) + 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 = 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++ = 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; 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); } Index: head/usr.sbin/lpr/lpd/printjob.c =================================================================== --- head/usr.sbin/lpr/lpd/printjob.c (revision 297794) +++ head/usr.sbin/lpr/lpd/printjob.c (revision 297795) @@ -1,2020 +1,2020 @@ /* * 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. * 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. */ #ifndef lint static const char copyright[] = "@(#) Copyright (c) 1983, 1993\n\ The Regents of the University of California. All rights reserved.\n"; #endif /* not lint */ #if 0 #ifndef lint static char sccsid[] = "@(#)printjob.c 8.7 (Berkeley) 5/10/95"; #endif /* not lint */ #endif #include "lp.cdefs.h" /* A cross-platform version of */ __FBSDID("$FreeBSD$"); /* * 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 #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #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 * * getline reads a line and expands tabs to blanks */ /* pass 1 */ while (getline(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 (getline(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] = 0; + 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] = 0; + 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 (getline(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 = getline(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 (getline(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 : ""); 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 : "", 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)) != 0) { + 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 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) */ }