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