1/*-
2 * SPDX-License-Identifier: BSD-3-Clause
3 *
4 * Copyright (c) 1983, 1993
5 *	The Regents of the University of California.  All rights reserved.
6 *
7 * Redistribution and use in source and binary forms, with or without
8 * modification, are permitted provided that the following conditions
9 * are met:
10 * 1. Redistributions of source code must retain the above copyright
11 *    notice, this list of conditions and the following disclaimer.
12 * 2. Redistributions in binary form must reproduce the above copyright
13 *    notice, this list of conditions and the following disclaimer in the
14 *    documentation and/or other materials provided with the distribution.
15 * 3. Neither the name of the University nor the names of its contributors
16 *    may be used to endorse or promote products derived from this software
17 *    without specific prior written permission.
18 *
19 * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
20 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
21 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
22 * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
23 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
24 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
25 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
26 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
27 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
28 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
29 * SUCH DAMAGE.
30 */
31
32#include "lp.cdefs.h"		/* A cross-platform version of <sys/cdefs.h> */
33#include <sys/param.h>
34#include <sys/stat.h>
35
36#include <ctype.h>
37#include <dirent.h>
38#include <err.h>
39#include <errno.h>
40#include <fcntl.h>
41#include <signal.h>
42#include <stdio.h>
43#include <stdlib.h>
44#include <string.h>
45#define psignal foil_gcc_psignal
46#define	sys_siglist foil_gcc_siglist
47#include <unistd.h>
48#undef psignal
49#undef sys_siglist
50
51#include "lp.h"
52#include "lp.local.h"
53#include "pathnames.h"
54
55/*
56 * Routines to display the state of the queue.
57 */
58#define JOBCOL	40		/* column for job # in -l format */
59#define OWNCOL	7		/* start of Owner column in normal */
60#define SIZCOL	62		/* start of Size column in normal */
61
62/*
63 * isprint() takes a parameter of 'int', but expect values in the range
64 * of unsigned char.  Define a wrapper which takes a value of type 'char',
65 * whether signed or unsigned, and ensure it ends up in the right range.
66 */
67#define	isprintch(Anychar) isprint((u_char)(Anychar))
68
69/*
70 * Stuff for handling job specifications
71 */
72static int	col;		/* column on screen */
73static char	current[MAXNAMLEN+1];	/* current file being printed */
74static char	file[MAXNAMLEN+1];	/* print file name */
75static int	first;		/* first file in ``files'' column? */
76static int	garbage;	/* # of garbage cf files */
77static int	lflag;		/* long output option */
78static int	rank;		/* order to be printed (-1=none, 0=active) */
79static long	totsize;	/* total print job size in bytes */
80
81static const char  *head0 = "Rank   Owner      Job  Files";
82static const char  *head1 = "Total Size\n";
83
84static void	alarmhandler(int _signo);
85static void	filtered_write(char *_obuffer, int _wlen, FILE *_wstream);
86static void	daemonwarn(const struct printer *_pp);
87
88/*
89 * Display the current state of the queue. Format = 1 if long format.
90 */
91void
92displayq(struct printer *pp, int format)
93{
94	register struct jobqueue *q;
95	register int i, nitems, fd, ret;
96	char *cp, *endp;
97	struct jobqueue **queue;
98	struct stat statb;
99	FILE *fp;
100	void (*savealrm)(int);
101
102	lflag = format;
103	totsize = 0;
104	rank = -1;
105
106	if ((cp = checkremote(pp))) {
107		printf("Warning: %s\n", cp);
108		free(cp);
109	}
110
111	/*
112	 * Print out local queue
113	 * Find all the control files in the spooling directory
114	 */
115	PRIV_START
116	if (chdir(pp->spool_dir) < 0)
117		fatal(pp, "cannot chdir to spooling directory: %s",
118		      strerror(errno));
119	PRIV_END
120	if ((nitems = getq(pp, &queue)) < 0)
121		fatal(pp, "cannot examine spooling area\n");
122	PRIV_START
123	ret = stat(pp->lock_file, &statb);
124	PRIV_END
125	if (ret >= 0) {
126		if (statb.st_mode & LFM_PRINT_DIS) {
127			if (pp->remote)
128				printf("%s: ", local_host);
129			printf("Warning: %s is down: ", pp->printer);
130			PRIV_START
131			fd = open(pp->status_file, O_RDONLY|O_SHLOCK);
132			PRIV_END
133			if (fd >= 0) {
134				while ((i = read(fd, line, sizeof(line))) > 0)
135					(void) fwrite(line, 1, i, stdout);
136				(void) close(fd);	/* unlocks as well */
137			} else
138				putchar('\n');
139		}
140		if (statb.st_mode & LFM_QUEUE_DIS) {
141			if (pp->remote)
142				printf("%s: ", local_host);
143			printf("Warning: %s queue is turned off\n",
144			       pp->printer);
145		}
146	}
147
148	if (nitems) {
149		PRIV_START
150		fp = fopen(pp->lock_file, "r");
151		PRIV_END
152		if (fp == NULL)
153			daemonwarn(pp);
154		else {
155			/* get daemon pid */
156			cp = current;
157			endp = cp + sizeof(current) - 1;
158			while ((i = getc(fp)) != EOF && i != '\n') {
159				if (cp < endp)
160					*cp++ = i;
161			}
162			*cp = '\0';
163			i = atoi(current);
164			if (i <= 0) {
165				ret = -1;
166			} else {
167				PRIV_START
168				ret = kill(i, 0);
169				PRIV_END
170			}
171			if (ret < 0) {
172				daemonwarn(pp);
173			} else {
174				/* read current file name */
175				cp = current;
176				endp = cp + sizeof(current) - 1;
177				while ((i = getc(fp)) != EOF && i != '\n') {
178					if (cp < endp)
179						*cp++ = i;
180				}
181				*cp = '\0';
182				/*
183				 * Print the status file.
184				 */
185				if (pp->remote)
186					printf("%s: ", local_host);
187				PRIV_START
188				fd = open(pp->status_file, O_RDONLY|O_SHLOCK);
189				PRIV_END
190				if (fd >= 0) {
191					while ((i = read(fd, line,
192							 sizeof(line))) > 0)
193						fwrite(line, 1, i, stdout);
194					close(fd);	/* unlocks as well */
195				} else
196					putchar('\n');
197			}
198			(void) fclose(fp);
199		}
200		/*
201		 * Now, examine the control files and print out the jobs to
202		 * be done for each user.
203		 */
204		if (!lflag)
205			header();
206		for (i = 0; i < nitems; i++) {
207			q = queue[i];
208			inform(pp, q->job_cfname);
209			free(q);
210		}
211		free(queue);
212	}
213	if (!pp->remote) {
214		if (nitems == 0)
215			puts("no entries");
216		return;
217	}
218
219	/*
220	 * Print foreign queue
221	 * Note that a file in transit may show up in either queue.
222	 */
223	if (nitems)
224		putchar('\n');
225	(void) snprintf(line, sizeof(line), "%c%s", format ? '\4' : '\3',
226			pp->remote_queue);
227	cp = line;
228	for (i = 0; i < requests && cp-line+10 < sizeof(line) - 1; i++) {
229		cp += strlen(cp);
230		(void) sprintf(cp, " %d", requ[i]);
231	}
232	for (i = 0; i < users && cp - line + 1 + strlen(user[i]) <
233		sizeof(line) - 1; i++) {
234		cp += strlen(cp);
235		*cp++ = ' ';
236		(void) strcpy(cp, user[i]);
237	}
238	strcat(line, "\n");
239	savealrm = signal(SIGALRM, alarmhandler);
240	alarm(pp->conn_timeout);
241	fd = getport(pp, pp->remote_host, 0);
242	alarm(0);
243	(void)signal(SIGALRM, savealrm);
244	if (fd < 0) {
245		if (from_host != local_host)
246			printf("%s: ", local_host);
247		printf("connection to %s is down\n", pp->remote_host);
248	}
249	else {
250		i = strlen(line);
251		if (write(fd, line, i) != i)
252			fatal(pp, "Lost connection");
253		while ((i = read(fd, line, sizeof(line))) > 0)
254			filtered_write(line, i, stdout);
255		filtered_write(NULL, -1, stdout);
256		(void) close(fd);
257	}
258}
259
260/*
261 * The lpq-info read from remote hosts may contain unprintable characters,
262 * or carriage-returns instead of line-feeds.  Clean those up before echoing
263 * the lpq-info line(s) to stdout.  The info may also be missing any kind of
264 * end-of-line character.  This also turns CRLF and LFCR into a plain LF.
265 *
266 * This routine may be called multiple times to process a single set of
267 * information, and after a set is finished this routine must be called
268 * one extra time with NULL specified as the buffer address.
269 */
270static void
271filtered_write(char *wbuffer, int wlen, FILE *wstream)
272{
273	static char lastchar, savedchar;
274	char *chkptr, *dest_end, *dest_ch, *nxtptr, *w_end;
275	int destlen;
276	char destbuf[BUFSIZ];
277
278	if (wbuffer == NULL) {
279		if (savedchar != '\0') {
280			if (savedchar == '\r')
281				savedchar = '\n';
282			fputc(savedchar, wstream);
283			lastchar = savedchar;
284			savedchar = '\0';
285		}
286		if (lastchar != '\0' && lastchar != '\n')
287			fputc('\n', wstream);
288		lastchar = '\0';
289		return;
290	}
291
292	dest_ch = &destbuf[0];
293	dest_end = dest_ch + sizeof(destbuf);
294	chkptr = wbuffer;
295	w_end = wbuffer + wlen;
296	lastchar = '\0';
297	if (savedchar != '\0') {
298		chkptr = &savedchar;
299		nxtptr = wbuffer;
300	} else
301		nxtptr = chkptr + 1;
302
303	while (chkptr < w_end) {
304		if (nxtptr < w_end) {
305			if ((*chkptr == '\r' && *nxtptr == '\n') ||
306			    (*chkptr == '\n' && *nxtptr == '\r')) {
307				*dest_ch++ = '\n';
308				/* want to skip past that second character */
309				nxtptr++;
310				goto check_next;
311			}
312		} else {
313			/* This is the last byte in the buffer given on this
314			 * call, so check if it could be the first-byte of a
315			 * significant two-byte sequence.  If it is, then
316			 * don't write it out now, but save for checking in
317			 * the next call.
318			 */
319			savedchar = '\0';
320			if (*chkptr == '\r' || *chkptr == '\n') {
321				savedchar = *chkptr;
322				break;
323			}
324		}
325		if (*chkptr == '\r')
326			*dest_ch++ = '\n';
327#if 0		/* XXX - don't translate unprintable characters (yet) */
328		else if (*chkptr != '\t' && *chkptr != '\n' &&
329		    !isprintch(*chkptr))
330			*dest_ch++ = '?';
331#endif
332		else
333			*dest_ch++ = *chkptr;
334
335check_next:
336		chkptr = nxtptr;
337		nxtptr = chkptr + 1;
338		if (dest_ch >= dest_end) {
339			destlen = dest_ch - &destbuf[0];
340			fwrite(destbuf, 1, destlen, wstream);
341			lastchar = destbuf[destlen - 1];
342			dest_ch = &destbuf[0];
343		}
344	}
345	destlen = dest_ch - &destbuf[0];
346	if (destlen > 0) {
347		fwrite(destbuf, 1, destlen, wstream);
348		lastchar = destbuf[destlen - 1];
349	}
350}
351
352/*
353 * Print a warning message if there is no daemon present.
354 */
355static void
356daemonwarn(const struct printer *pp)
357{
358	if (pp->remote)
359		printf("%s: ", local_host);
360	puts("Warning: no daemon present");
361	current[0] = '\0';
362}
363
364/*
365 * Print the header for the short listing format
366 */
367void
368header(void)
369{
370	printf("%s", head0);
371	col = strlen(head0)+1;
372	blankfill(SIZCOL);
373	printf("%s", head1);
374}
375
376void
377inform(const struct printer *pp, char *cf)
378{
379	int copycnt, jnum;
380	char	 savedname[MAXPATHLEN+1];
381	FILE	*cfp;
382
383	/*
384	 * There's a chance the control file has gone away
385	 * in the meantime; if this is the case just keep going
386	 */
387	PRIV_START
388	if ((cfp = fopen(cf, "r")) == NULL)
389		return;
390	PRIV_END
391
392	if (rank < 0)
393		rank = 0;
394	if (pp->remote || garbage || strcmp(cf, current))
395		rank++;
396
397	/*
398	 * The cf-file may include commands to print more than one datafile
399	 * from the user.  For each datafile, the cf-file contains at least
400	 * one line which starts with some format-specifier ('a'-'z'), and
401	 * a second line ('N'ame) which indicates the original name the user
402	 * specified for that file.  There can be multiple format-spec lines
403	 * for a single Name-line, if the user requested multiple copies of
404	 * that file.  Standard lpr puts the format-spec line(s) before the
405	 * Name-line, while lprNG puts the Name-line before the format-spec
406	 * line(s).  This section needs to handle the lines in either order.
407	 */
408	copycnt = 0;
409	file[0] = '\0';
410	savedname[0] = '\0';
411	jnum = calc_jobnum(cf, NULL);
412	while (get_line(cfp)) {
413		switch (line[0]) {
414		case 'P': /* Was this file specified in the user's list? */
415			if (!inlist(line+1, cf)) {
416				fclose(cfp);
417				return;
418			}
419			if (lflag) {
420				printf("\n%s: ", line+1);
421				col = strlen(line+1) + 2;
422				prank(rank);
423				blankfill(JOBCOL);
424				printf(" [job %s]\n", cf+3);
425			} else {
426				col = 0;
427				prank(rank);
428				blankfill(OWNCOL);
429				printf("%-10s %-3d  ", line+1, jnum);
430				col += 16;
431				first = 1;
432			}
433			continue;
434		default: /* some format specifer and file name? */
435			if (line[0] < 'a' || line[0] > 'z')
436				break;
437			if (copycnt == 0 || strcmp(file, line+1) != 0) {
438				strlcpy(file, line + 1, sizeof(file));
439			}
440			copycnt++;
441			/*
442			 * deliberately 'continue' to another get_line(), so
443			 * all format-spec lines for this datafile are read
444			 * in and counted before calling show()
445			 */
446			continue;
447		case 'N':
448			strlcpy(savedname, line + 1, sizeof(savedname));
449			break;
450		}
451		if ((file[0] != '\0') && (savedname[0] != '\0')) {
452			show(savedname, file, copycnt);
453			copycnt = 0;
454			file[0] = '\0';
455			savedname[0] = '\0';
456		}
457	}
458	fclose(cfp);
459	/* check for a file which hasn't been shown yet */
460	if (file[0] != '\0') {
461		if (savedname[0] == '\0') {
462			/* a safeguard in case the N-ame line is missing */
463			strlcpy(savedname, file, sizeof(savedname));
464		}
465		show(savedname, file, copycnt);
466	}
467	if (!lflag) {
468		blankfill(SIZCOL);
469		printf("%ld bytes\n", totsize);
470		totsize = 0;
471	}
472}
473
474int
475inlist(char *uname, char *cfile)
476{
477	int *r, jnum;
478	char **u;
479	const char *cfhost;
480
481	if (users == 0 && requests == 0)
482		return(1);
483	/*
484	 * Check to see if it's in the user list
485	 */
486	for (u = user; u < &user[users]; u++)
487		if (!strcmp(*u, uname))
488			return(1);
489	/*
490	 * Check the request list
491	 */
492	jnum = calc_jobnum(cfile, &cfhost);
493	for (r = requ; r < &requ[requests]; r++)
494		if (*r == jnum && !strcmp(cfhost, from_host))
495			return(1);
496	return(0);
497}
498
499void
500show(const char *nfile, const char *datafile, int copies)
501{
502	if (strcmp(nfile, " ") == 0)
503		nfile = "(standard input)";
504	if (lflag)
505		ldump(nfile, datafile, copies);
506	else
507		dump(nfile, datafile, copies);
508}
509
510/*
511 * Fill the line with blanks to the specified column
512 */
513void
514blankfill(int tocol)
515{
516	while (col++ < tocol)
517		putchar(' ');
518}
519
520/*
521 * Give the abbreviated dump of the file names
522 */
523void
524dump(const char *nfile, const char *datafile, int copies)
525{
526	struct stat lbuf;
527	const char etctmpl[] = ", ...";
528	char	 etc[sizeof(etctmpl)];
529	char	*lastsep;
530	short	 fill, nlen;
531	short	 rem, remetc;
532
533	/*
534	 * Print as many filenames as will fit
535	 *      (leaving room for the 'total size' field)
536	 */
537	fill = first ? 0 : 2;	/* fill space for ``, '' */
538	nlen = strlen(nfile);
539	rem = SIZCOL - 1 - col;
540	if (nlen + fill > rem) {
541		if (first) {
542			/* print the right-most part of the name */
543			printf("...%s ", &nfile[3+nlen-rem]);
544			col = SIZCOL;
545		} else if (rem > 0) {
546			/* fit as much of the etc-string as we can */
547			remetc = rem;
548			if (rem > strlen(etctmpl))
549				remetc = strlen(etctmpl);
550			etc[0] = '\0';
551			strncat(etc, etctmpl, remetc);
552			printf("%s", etc);
553			col += remetc;
554			rem -= remetc;
555			/* room for the last segment of this filename? */
556			lastsep = strrchr(nfile, '/');
557			if ((lastsep != NULL) && (rem > strlen(lastsep))) {
558				/* print the right-most part of this name */
559				printf("%s", lastsep);
560				col += strlen(lastsep);
561			} else {
562				/* do not pack any more names in here */
563				blankfill(SIZCOL);
564			}
565		}
566	} else {
567		if (!first)
568			printf(", ");
569		printf("%s", nfile);
570		col += nlen + fill;
571	}
572	first = 0;
573
574	PRIV_START
575	if (*datafile && !stat(datafile, &lbuf))
576		totsize += copies * lbuf.st_size;
577	PRIV_END
578}
579
580/*
581 * Print the long info about the file
582 */
583void
584ldump(const char *nfile, const char *datafile, int copies)
585{
586	struct stat lbuf;
587
588	putchar('\t');
589	if (copies > 1)
590		printf("%-2d copies of %-19s", copies, nfile);
591	else
592		printf("%-32s", nfile);
593	if (*datafile && !stat(datafile, &lbuf))
594		printf(" %qd bytes", (long long) lbuf.st_size);
595	else
596		printf(" ??? bytes");
597	putchar('\n');
598}
599
600/*
601 * Print the job's rank in the queue,
602 *   update col for screen management
603 */
604void
605prank(int n)
606{
607	char rline[100];
608	static const char *r[] = {
609		"th", "st", "nd", "rd", "th", "th", "th", "th", "th", "th"
610	};
611
612	if (n == 0) {
613		printf("active");
614		col += 6;
615		return;
616	}
617	if ((n/10)%10 == 1)
618		(void)snprintf(rline, sizeof(rline), "%dth", n);
619	else
620		(void)snprintf(rline, sizeof(rline), "%d%s", n, r[n%10]);
621	col += strlen(rline);
622	printf("%s", rline);
623}
624
625void
626alarmhandler(int signo __unused)
627{
628	/* the signal is ignored */
629	/* (the '__unused' is just to avoid a compile-time warning) */
630}
631