xref: /freebsd/usr.sbin/lpr/lpc/cmds.c (revision 41466b50c1d5bfd1cf6adaae547a579a75d7c04e)
1 /*
2  * Copyright (c) 1983, 1993
3  *	The Regents of the University of California.  All rights reserved.
4  *
5  *
6  * Redistribution and use in source and binary forms, with or without
7  * modification, are permitted provided that the following conditions
8  * are met:
9  * 1. Redistributions of source code must retain the above copyright
10  *    notice, this list of conditions and the following disclaimer.
11  * 2. Redistributions in binary form must reproduce the above copyright
12  *    notice, this list of conditions and the following disclaimer in the
13  *    documentation and/or other materials provided with the distribution.
14  * 3. All advertising materials mentioning features or use of this software
15  *    must display the following acknowledgement:
16  *	This product includes software developed by the University of
17  *	California, Berkeley and its contributors.
18  * 4. Neither the name of the University nor the names of its contributors
19  *    may be used to endorse or promote products derived from this software
20  *    without specific prior written permission.
21  *
22  * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
23  * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
24  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
25  * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
26  * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
27  * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
28  * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
29  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
30  * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
31  * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
32  * SUCH DAMAGE.
33  */
34 
35 #ifndef lint
36 static const char copyright[] =
37 "@(#) Copyright (c) 1983, 1993\n\
38 	The Regents of the University of California.  All rights reserved.\n";
39 #endif /* not lint */
40 
41 #ifndef lint
42 /*
43 static char sccsid[] = "@(#)cmds.c	8.2 (Berkeley) 4/28/95";
44 */
45 static const char rcsid[] =
46   "$FreeBSD$";
47 #endif /* not lint */
48 
49 /*
50  * lpc -- line printer control program -- commands:
51  */
52 
53 #include <sys/param.h>
54 #include <sys/time.h>
55 #include <sys/stat.h>
56 #include <sys/file.h>
57 
58 #include <signal.h>
59 #include <fcntl.h>
60 #include <errno.h>
61 #include <dirent.h>
62 #include <unistd.h>
63 #include <stdlib.h>
64 #include <stdio.h>
65 #include <ctype.h>
66 #include <string.h>
67 #include "lp.h"
68 #include "lp.local.h"
69 #include "lpc.h"
70 #include "extern.h"
71 #include "pathnames.h"
72 
73 static void	 abortpr(struct printer *_pp, int _dis);
74 static int	 doarg(char *_job);
75 static int	 doselect(struct dirent *_d);
76 static void	 putmsg(struct printer *_pp, int _argc, char **_argv);
77 static int	 sortq(const void *_a, const void *_b);
78 static void	 startpr(struct printer *_pp, int _chgenable);
79 static int	 touch(struct jobqueue *_jq);
80 static void	 unlinkf(char *_name);
81 static void	 upstat(struct printer *_pp, const char *_msg);
82 static void	 wrapup_clean(int _laststatus);
83 
84 /*
85  * generic framework for commands which operate on all or a specified
86  * set of printers
87  */
88 enum	qsel_val {			/* how a given ptr was selected */
89 	QSEL_UNKNOWN = -1,		/* ... not selected yet */
90 	QSEL_BYNAME = 0,		/* ... user specifed it by name */
91 	QSEL_ALL = 1			/* ... user wants "all" printers */
92 					/*     (with more to come)    */
93 };
94 
95 static enum qsel_val generic_qselect;	/* indicates how ptr was selected */
96 static int generic_initerr;		/* result of initrtn processing */
97 static char *generic_nullarg;
98 static void (*generic_wrapup)(int _last_status);   /* perform rtn wrap-up */
99 
100 void
101 generic(void (*specificrtn)(struct printer *_pp),
102     void (*initrtn)(int _argc, char *_argv[]), int argc, char *argv[])
103 {
104 	int cmdstatus, more, targc;
105 	struct printer myprinter, *pp;
106 	char **targv;
107 
108 	if (argc == 1) {
109 		printf("Usage: %s {all | printer ...}\n", argv[0]);
110 		return;
111 	}
112 
113 	/*
114 	 * The initialization routine for a command might set a generic
115 	 * "wrapup" routine, which should be called after processing all
116 	 * the printers in the command.  This might print summary info.
117 	 *
118 	 * Note that the initialization routine may also parse (and
119 	 * nullify) some of the parameters given on the command, leaving
120 	 * only the parameters which have to do with printer names.
121 	 */
122 	pp = &myprinter;
123 	generic_wrapup = NULL;
124 	generic_qselect = QSEL_UNKNOWN;
125 	cmdstatus = 0;
126 	/* this just needs to be a distinct value of type 'char *' */
127 	if (generic_nullarg == NULL)
128 		generic_nullarg = strdup("");
129 
130 	/* call initialization routine, if there is one for this cmd */
131 	if (initrtn != NULL) {
132 		generic_initerr = 0;
133 		(*initrtn)(argc, argv);
134 		if (generic_initerr)
135 			return;
136 		/* skip any initial arguments null-ified by initrtn */
137 		targc = argc;
138 		targv = argv;
139 		while (--targc) {
140 			if (targv[1] != generic_nullarg)
141 				break;
142 			++targv;
143 		}
144 		if (targv != argv) {
145 			targv[0] = argv[0];	/* copy the command-name */
146 			argv = targv;
147 			argc = targc + 1;
148 		}
149 	}
150 
151 	if (argc == 2 && strcmp(argv[1], "all") == 0) {
152 		generic_qselect = QSEL_ALL;
153 		more = firstprinter(pp, &cmdstatus);
154 		if (cmdstatus)
155 			goto looperr;
156 		while (more) {
157 			(*specificrtn)(pp);
158 			do {
159 				more = nextprinter(pp, &cmdstatus);
160 looperr:
161 				switch (cmdstatus) {
162 				case PCAPERR_TCOPEN:
163 					printf("warning: %s: unresolved "
164 					       "tc= reference(s) ",
165 					       pp->printer);
166 				case PCAPERR_SUCCESS:
167 					break;
168 				default:
169 					fatal(pp, "%s", pcaperr(cmdstatus));
170 				}
171 			} while (more && cmdstatus);
172 		}
173 		goto wrapup;
174 	}
175 
176 	generic_qselect = QSEL_BYNAME;		/* specifically-named ptrs */
177 	while (--argc) {
178 		++argv;
179 		if (*argv == generic_nullarg)
180 			continue;
181 		init_printer(pp);
182 		cmdstatus = getprintcap(*argv, pp);
183 		switch (cmdstatus) {
184 		default:
185 			fatal(pp, "%s", pcaperr(cmdstatus));
186 		case PCAPERR_NOTFOUND:
187 			printf("unknown printer %s\n", *argv);
188 			continue;
189 		case PCAPERR_TCOPEN:
190 			printf("warning: %s: unresolved tc= reference(s)\n",
191 			       *argv);
192 			break;
193 		case PCAPERR_SUCCESS:
194 			break;
195 		}
196 		(*specificrtn)(pp);
197 	}
198 
199 wrapup:
200 	if (generic_wrapup) {
201 		(*generic_wrapup)(cmdstatus);
202 	}
203 
204 }
205 
206 /*
207  * kill an existing daemon and disable printing.
208  */
209 void
210 doabort(struct printer *pp)
211 {
212 	abortpr(pp, 1);
213 }
214 
215 static void
216 abortpr(struct printer *pp, int dis)
217 {
218 	register FILE *fp;
219 	struct stat stbuf;
220 	int pid, fd;
221 	char lf[MAXPATHLEN];
222 
223 	lock_file_name(pp, lf, sizeof lf);
224 	printf("%s:\n", pp->printer);
225 
226 	/*
227 	 * Turn on the owner execute bit of the lock file to disable printing.
228 	 */
229 	if (dis) {
230 		seteuid(euid);
231 		if (stat(lf, &stbuf) >= 0) {
232 			if (chmod(lf, stbuf.st_mode | LFM_PRINT_DIS) < 0)
233 				printf("\tcannot disable printing: %s\n",
234 				       strerror(errno));
235 			else {
236 				upstat(pp, "printing disabled\n");
237 				printf("\tprinting disabled\n");
238 			}
239 		} else if (errno == ENOENT) {
240 			if ((fd = open(lf, O_WRONLY|O_CREAT,
241 				       LOCK_FILE_MODE | LFM_PRINT_DIS)) < 0)
242 				printf("\tcannot create lock file: %s\n",
243 				       strerror(errno));
244 			else {
245 				(void) close(fd);
246 				upstat(pp, "printing disabled\n");
247 				printf("\tprinting disabled\n");
248 				printf("\tno daemon to abort\n");
249 			}
250 			goto out;
251 		} else {
252 			printf("\tcannot stat lock file\n");
253 			goto out;
254 		}
255 	}
256 	/*
257 	 * Kill the current daemon to stop printing now.
258 	 */
259 	if ((fp = fopen(lf, "r")) == NULL) {
260 		printf("\tcannot open lock file\n");
261 		goto out;
262 	}
263 	if (!getline(fp) || flock(fileno(fp), LOCK_SH|LOCK_NB) == 0) {
264 		(void) fclose(fp);	/* unlocks as well */
265 		printf("\tno daemon to abort\n");
266 		goto out;
267 	}
268 	(void) fclose(fp);
269 	if (kill(pid = atoi(line), SIGTERM) < 0) {
270 		if (errno == ESRCH)
271 			printf("\tno daemon to abort\n");
272 		else
273 			printf("\tWarning: daemon (pid %d) not killed\n", pid);
274 	} else
275 		printf("\tdaemon (pid %d) killed\n", pid);
276 out:
277 	seteuid(uid);
278 }
279 
280 /*
281  * Write a message into the status file.
282  */
283 static void
284 upstat(struct printer *pp, const char *msg)
285 {
286 	register int fd;
287 	char statfile[MAXPATHLEN];
288 
289 	status_file_name(pp, statfile, sizeof statfile);
290 	umask(0);
291 	fd = open(statfile, O_WRONLY|O_CREAT|O_EXLOCK, STAT_FILE_MODE);
292 	if (fd < 0) {
293 		printf("\tcannot create status file: %s\n", strerror(errno));
294 		return;
295 	}
296 	(void) ftruncate(fd, 0);
297 	if (msg == (char *)NULL)
298 		(void) write(fd, "\n", 1);
299 	else
300 		(void) write(fd, msg, strlen(msg));
301 	(void) close(fd);
302 }
303 
304 /*
305  * "global" variables for all the routines related to 'clean' and 'tclean'
306  */
307 static time_t	 cln_now;		/* current time */
308 static double	 cln_minage;		/* minimum age before file is removed */
309 static long	 cln_sizecnt;		/* amount of space freed up */
310 static int 	 cln_debug;		/* print extra debugging msgs */
311 static int	 cln_filecnt;		/* number of files destroyed */
312 static int	 cln_foundcore;		/* found a core file! */
313 static int	 cln_queuecnt;		/* number of queues checked */
314 static int 	 cln_testonly;		/* remove-files vs just-print-info */
315 
316 static int
317 doselect(struct dirent *d)
318 {
319 	int c = d->d_name[0];
320 
321 	if ((c == 't' || c == 'c' || c == 'd') && d->d_name[1] == 'f')
322 		return 1;
323 	if (c == 'c') {
324 		if (!strcmp(d->d_name, "core"))
325 			cln_foundcore = 1;
326 	}
327 	if (c == 'e') {
328 		if (!strncmp(d->d_name, "errs.", 5))
329 			return 1;
330 	}
331 	return 0;
332 }
333 
334 /*
335  * Comparison routine that clean_q() uses for scandir.
336  *
337  * The purpose of this sort is to have all `df' files end up immediately
338  * after the matching `cf' file.  For files matching `cf', `df', or `tf',
339  * it sorts by job number and machine, then by `cf', `df', or `tf', then
340  * by the sequence letter A-Z, a-z.    This routine may also see filenames
341  * which do not start with `cf', `df', or `tf' (such as `errs.*'), and
342  * those are simply sorted by the full filename.
343  */
344 static int
345 sortq(const void *a, const void *b)
346 {
347 	const int a_lt_b = -1, a_gt_b = 1, cat_other = 10;
348 	const char *fname_a, *fname_b, *jnum_a, *jnum_b;
349 	int cat_a, cat_b, ch, res, seq_a, seq_b;
350 
351 	fname_a = (*(const struct dirent **)a)->d_name;
352 	fname_b = (*(const struct dirent **)b)->d_name;
353 
354 	/*
355 	 * First separate filenames into cagatories.  Catagories are
356 	 * legitimate `cf', `df', & `tf' filenames, and "other" - in
357 	 * that order.  It is critical that the mapping be exactly
358 	 * the same for 'a' vs 'b', so define a macro for the job.
359 	 *
360 	 * [aside: the standard `cf' file has the jobnumber start in
361 	 * position 4, but some implementations have that as an extra
362 	 * file-sequence letter, and start the job number in position 5.]
363 	 */
364 #define MAP_TO_CAT(fname_X,cat_X,jnum_X,seq_X) do { \
365 	cat_X = cat_other;    \
366 	ch = *(fname_X + 2);  \
367 	jnum_X = fname_X + 3; \
368 	seq_X = 0;            \
369 	if ((*(fname_X + 1) == 'f') && (isalpha(ch))) { \
370 		seq_X = ch; \
371 		if (*fname_X == 'c') \
372 			cat_X = 1; \
373 		else if (*fname_X == 'd') \
374 			cat_X = 2; \
375 		else if (*fname_X == 't') \
376 			cat_X = 3; \
377 		if (cat_X != cat_other) { \
378 			ch = *jnum_X; \
379 			if (!isdigit(ch)) { \
380 				if (isalpha(ch)) { \
381 					jnum_X++; \
382 					ch = *jnum_X; \
383 					seq_X = (seq_X << 8) + ch; \
384 				} \
385 				if (!isdigit(ch)) \
386 					cat_X = cat_other; \
387 			} \
388 		} \
389 	} \
390 } while (0)
391 
392 	MAP_TO_CAT(fname_a, cat_a, jnum_a, seq_a);
393 	MAP_TO_CAT(fname_b, cat_b, jnum_b, seq_b);
394 
395 #undef MAP_TO_CAT
396 
397 	/* First handle all cases which have "other" files */
398 	if ((cat_a >= cat_other) || (cat_b >= cat_other)) {
399 		/* for two "other" files, just compare the full name */
400 		if (cat_a == cat_b)
401 			res = strcmp(fname_a, fname_b);
402 		else if (cat_a < cat_b)
403 			res = a_lt_b;
404 		else
405 			res = a_gt_b;
406 		goto have_res;
407 	}
408 
409 	/*
410 	 * At this point, we know both files are legitimate `cf', `df',
411 	 * or `tf' files.  Compare them by job-number and machine name.
412 	 */
413 	res = strcmp(jnum_a, jnum_b);
414 	if (res != 0)
415 		goto have_res;
416 
417 	/*
418 	 * We have two files which belong to the same job.  Sort based
419 	 * on the catagory of file (`c' before `d', etc).
420 	 */
421 	if (cat_a < cat_b) {
422 		res = a_lt_b;
423 		goto have_res;
424 	} else if (cat_a > cat_b) {
425 		res = a_gt_b;
426 		goto have_res;
427 	}
428 
429 	/*
430 	 * Two files in the same catagory for a single job.  Sort based
431 	 * on the sequence letter(s).  (usually `A' thru `Z', etc).
432 	 */
433 	if (seq_a < seq_b) {
434 		res = a_lt_b;
435 		goto have_res;
436 	} else if (seq_a > seq_b) {
437 		res = a_gt_b;
438 		goto have_res;
439 	}
440 
441 	/*
442 	 * Given that the filenames in a directory are unique, this SHOULD
443 	 * never happen (unless there are logic errors in this routine).
444 	 * But if it does happen, we must return "is equal" or the caller
445 	 * might see inconsistent results in the sorting order, and that
446 	 * can trigger other problems.
447 	 */
448 	printf("\t*** Error in sortq: %s == %s !\n", fname_a, fname_b);
449 	printf("\t***       cat %d == %d ; seq = %d %d\n", cat_a, cat_b,
450 	    seq_a, seq_b);
451 	res = 0;
452 
453 have_res:
454 	return res;
455 }
456 
457 /*
458  * Remove all spool files and temporaries from the spooling area.
459  * Or, perhaps:
460  * Remove incomplete jobs from spooling area.
461  */
462 
463 void
464 init_clean(int argc, char *argv[])
465 {
466 
467 	/* init some fields before 'clean' is called for each queue */
468 	cln_queuecnt = 0;
469 	cln_now = time(NULL);
470 	cln_minage = 3600.0;		/* only delete files >1h old */
471 	cln_filecnt = 0;
472 	cln_sizecnt = 0;
473 	cln_debug = 0;
474 	cln_testonly = 0;
475 	generic_wrapup = &wrapup_clean;
476 
477 	/* see if there are any options specified before the ptr list */
478 	while (--argc) {
479 		++argv;
480 		if (**argv != '-')
481 			break;
482 		if (strcmp(*argv, "-d") == 0) {
483 			/* just an example of an option... */
484 			cln_debug++;
485 			*argv = generic_nullarg;	/* "erase" it */
486 		} else {
487 			printf("Invalid option '%s'\n", *argv);
488 			generic_initerr = 1;
489 		}
490 	}
491 
492 	return;
493 }
494 
495 void
496 init_tclean(int argc, char *argv[])
497 {
498 
499 	/* only difference between 'clean' and 'tclean' is one value */
500 	/* (...and the fact that 'clean' is priv and 'tclean' is not) */
501 	init_clean(argc, argv);
502 	cln_testonly = 1;
503 
504 	return;
505 }
506 
507 void
508 clean_q(struct printer *pp)
509 {
510 	char *cp, *cp1, *lp;
511 	struct dirent **queue;
512 	size_t linerem;
513 	int didhead, i, n, nitems, rmcp;
514 
515 	cln_queuecnt++;
516 
517 	didhead = 0;
518 	if (generic_qselect == QSEL_BYNAME) {
519 		printf("%s:\n", pp->printer);
520 		didhead = 1;
521 	}
522 
523 	lp = line;
524 	cp = pp->spool_dir;
525 	while (lp < &line[sizeof(line) - 1]) {
526 		if ((*lp++ = *cp++) == 0)
527 			break;
528 	}
529 	lp[-1] = '/';
530 	linerem = sizeof(line) - (lp - line);
531 
532 	cln_foundcore = 0;
533 	seteuid(euid);
534 	nitems = scandir(pp->spool_dir, &queue, doselect, sortq);
535 	seteuid(uid);
536 	if (nitems < 0) {
537 		if (!didhead) {
538 			printf("%s:\n", pp->printer);
539 			didhead = 1;
540 		}
541 		printf("\tcannot examine spool directory\n");
542 		return;
543 	}
544 	if (cln_foundcore) {
545 		if (!didhead) {
546 			printf("%s:\n", pp->printer);
547 			didhead = 1;
548 		}
549 		printf("\t** found a core file in %s !\n", pp->spool_dir);
550 	}
551 	if (nitems == 0)
552 		return;
553 	if (!didhead)
554 		printf("%s:\n", pp->printer);
555 	if (cln_debug) {
556 		printf("\t** ----- Sorted list of files being checked:\n");
557 		i = 0;
558 		do {
559 			cp = queue[i]->d_name;
560 			printf("\t** [%3d] = %s\n", i, cp);
561 		} while (++i < nitems);
562 		printf("\t** ----- end of sorted list\n");
563 	}
564 	i = 0;
565 	do {
566 		cp = queue[i]->d_name;
567 		rmcp = 0;
568 		if (*cp == 'c') {
569 			/*
570 			 * A control file.  Look for matching data-files.
571 			 */
572 			/* XXX
573 			 *  Note the logic here assumes that the hostname
574 			 *  part of cf-filenames match the hostname part
575 			 *  in df-filenames, and that is not necessarily
576 			 *  true (eg: for multi-homed hosts).  This needs
577 			 *  some further thought...
578 			 */
579 			n = 0;
580 			while (i + 1 < nitems) {
581 				cp1 = queue[i + 1]->d_name;
582 				if (*cp1 != 'd' || strcmp(cp + 3, cp1 + 3))
583 					break;
584 				i++;
585 				n++;
586 			}
587 			if (n == 0) {
588 				rmcp = 1;
589 			}
590 		} else if (*cp == 'e') {
591 			/*
592 			 * Must be an errrs or email temp file.
593 			 */
594 			rmcp = 1;
595 		} else {
596 			/*
597 			 * Must be a df with no cf (otherwise, it would have
598 			 * been skipped above) or a tf file (which can always
599 			 * be removed if it's old enough).
600 			 */
601 			rmcp = 1;
602 		}
603 		if (rmcp) {
604 			if (strlen(cp) >= linerem) {
605 				printf("\t** internal error: 'line' overflow!\n");
606 				printf("\t**   spooldir = %s\n", pp->spool_dir);
607 				printf("\t**   cp = %s\n", cp);
608 				return;
609 			}
610 			strlcpy(lp, cp, linerem);
611 			unlinkf(line);
612 		}
613      	} while (++i < nitems);
614 }
615 
616 static void
617 wrapup_clean(int laststatus __unused)
618 {
619 
620 	printf("Checked %d queues, and ", cln_queuecnt);
621 	if (cln_filecnt < 1) {
622 		printf("no cruft was found\n");
623 		return;
624 	}
625 	if (cln_testonly) {
626 		printf("would have ");
627 	}
628 	printf("removed %d files (%ld bytes).\n", cln_filecnt, cln_sizecnt);
629 }
630 
631 static void
632 unlinkf(char *name)
633 {
634 	struct stat stbuf;
635 	double agemod, agestat;
636 	int res;
637 	char linkbuf[BUFSIZ];
638 
639 	/*
640 	 * We have to use lstat() instead of stat(), in case this is a df*
641 	 * "file" which is really a symlink due to 'lpr -s' processing.  In
642 	 * that case, we need to check the last-mod time of the symlink, and
643 	 * not the file that the symlink is pointed at.
644 	 */
645 	seteuid(euid);
646 	res = lstat(name, &stbuf);
647 	seteuid(uid);
648 	if (res < 0) {
649 		printf("\terror return from stat(%s):\n", name);
650 		printf("\t      %s\n", strerror(errno));
651 		return;
652 	}
653 
654 	agemod = difftime(cln_now, stbuf.st_mtime);
655 	agestat = difftime(cln_now,  stbuf.st_ctime);
656 	if (cln_debug > 1) {
657 		/* this debugging-aid probably is not needed any more... */
658 		printf("\t\t  modify age=%g secs, stat age=%g secs\n",
659 		    agemod, agestat);
660 	}
661 	if ((agemod <= cln_minage) && (agestat <= cln_minage))
662 		return;
663 
664 	/*
665 	 * if this file is a symlink, then find out the target of the
666 	 * symlink before unlink-ing the file itself
667 	 */
668 	if (S_ISLNK(stbuf.st_mode)) {
669 		seteuid(euid);
670 		res = readlink(name, linkbuf, sizeof(linkbuf));
671 		seteuid(uid);
672 		if (res < 0) {
673 			printf("\terror return from readlink(%s):\n", name);
674 			printf("\t      %s\n", strerror(errno));
675 			return;
676 		}
677 		if (res == sizeof(linkbuf))
678 			res--;
679 		linkbuf[res] = '\0';
680 	}
681 
682 	cln_filecnt++;
683 	cln_sizecnt += stbuf.st_size;
684 
685 	if (cln_testonly) {
686 		printf("\twould remove %s\n", name);
687 		if (S_ISLNK(stbuf.st_mode)) {
688 			printf("\t    (which is a symlink to %s)\n", linkbuf);
689 		}
690 	} else {
691 		seteuid(euid);
692 		res = unlink(name);
693 		seteuid(uid);
694 		if (res < 0)
695 			printf("\tcannot remove %s (!)\n", name);
696 		else
697 			printf("\tremoved %s\n", name);
698 		/* XXX
699 		 *  Note that for a df* file, this code should also check to see
700 		 *  if it is a symlink to some other file, and if the original
701 		 *  lpr command included '-r' ("remove file").  Of course, this
702 		 *  code would not be removing the df* file unless there was no
703 		 *  matching cf* file, and without the cf* file it is currently
704 		 *  impossible to determine if '-r' had been specified...
705 		 *
706 		 *  As a result of this quandry, we may be leaving behind a
707 		 *  user's file that was supposed to have been removed after
708 		 *  being printed.  This may effect services such as CAP or
709 		 *  samba, if they were configured to use 'lpr -r', and if
710 		 *  datafiles are not being properly removed.
711 		*/
712 		if (S_ISLNK(stbuf.st_mode)) {
713 			printf("\t    (which was a symlink to %s)\n", linkbuf);
714 		}
715 	}
716 }
717 
718 /*
719  * Enable queuing to the printer (allow lpr's).
720  */
721 void
722 enable(struct printer *pp)
723 {
724 	struct stat stbuf;
725 	char lf[MAXPATHLEN];
726 
727 	lock_file_name(pp, lf, sizeof lf);
728 	printf("%s:\n", pp->printer);
729 
730 	/*
731 	 * Turn off the group execute bit of the lock file to enable queuing.
732 	 */
733 	seteuid(euid);
734 	if (stat(lf, &stbuf) >= 0) {
735 		if (chmod(lf, stbuf.st_mode & ~LFM_QUEUE_DIS) < 0)
736 			printf("\tcannot enable queuing\n");
737 		else
738 			printf("\tqueuing enabled\n");
739 	}
740 	seteuid(uid);
741 }
742 
743 /*
744  * Disable queuing.
745  */
746 void
747 disable(struct printer *pp)
748 {
749 	register int fd;
750 	struct stat stbuf;
751 	char lf[MAXPATHLEN];
752 
753 	lock_file_name(pp, lf, sizeof lf);
754 	printf("%s:\n", pp->printer);
755 	/*
756 	 * Turn on the group execute bit of the lock file to disable queuing.
757 	 */
758 	seteuid(euid);
759 	if (stat(lf, &stbuf) >= 0) {
760 		if (chmod(lf, stbuf.st_mode | LFM_QUEUE_DIS) < 0)
761 			printf("\tcannot disable queuing: %s\n",
762 			       strerror(errno));
763 		else
764 			printf("\tqueuing disabled\n");
765 	} else if (errno == ENOENT) {
766 		if ((fd = open(lf, O_WRONLY|O_CREAT,
767 			       LOCK_FILE_MODE | LFM_QUEUE_DIS)) < 0)
768 			printf("\tcannot create lock file: %s\n",
769 			       strerror(errno));
770 		else {
771 			(void) close(fd);
772 			printf("\tqueuing disabled\n");
773 		}
774 	} else
775 		printf("\tcannot stat lock file\n");
776 	seteuid(uid);
777 }
778 
779 /*
780  * Disable queuing and printing and put a message into the status file
781  * (reason for being down).
782  */
783 void
784 down(int argc, char *argv[])
785 {
786         int cmdstatus, more;
787 	struct printer myprinter, *pp = &myprinter;
788 
789 	if (argc == 1) {
790 		printf("Usage: down {all | printer} [message ...]\n");
791 		return;
792 	}
793 	if (!strcmp(argv[1], "all")) {
794 		more = firstprinter(pp, &cmdstatus);
795 		if (cmdstatus)
796 			goto looperr;
797 		while (more) {
798 			putmsg(pp, argc - 2, argv + 2);
799 			do {
800 				more = nextprinter(pp, &cmdstatus);
801 looperr:
802 				switch (cmdstatus) {
803 				case PCAPERR_TCOPEN:
804 					printf("warning: %s: unresolved "
805 					       "tc= reference(s) ",
806 					       pp->printer);
807 				case PCAPERR_SUCCESS:
808 					break;
809 				default:
810 					fatal(pp, "%s", pcaperr(cmdstatus));
811 				}
812 			} while (more && cmdstatus);
813 		}
814 		return;
815 	}
816 	init_printer(pp);
817 	cmdstatus = getprintcap(argv[1], pp);
818 	switch (cmdstatus) {
819 	default:
820 		fatal(pp, "%s", pcaperr(cmdstatus));
821 	case PCAPERR_NOTFOUND:
822 		printf("unknown printer %s\n", argv[1]);
823 		return;
824 	case PCAPERR_TCOPEN:
825 		printf("warning: %s: unresolved tc= reference(s)", argv[1]);
826 		break;
827 	case PCAPERR_SUCCESS:
828 		break;
829 	}
830 	putmsg(pp, argc - 2, argv + 2);
831 }
832 
833 static void
834 putmsg(struct printer *pp, int argc, char **argv)
835 {
836 	register int fd;
837 	register char *cp1, *cp2;
838 	char buf[1024];
839 	char file[MAXPATHLEN];
840 	struct stat stbuf;
841 
842 	printf("%s:\n", pp->printer);
843 	/*
844 	 * Turn on the group execute bit of the lock file to disable queuing;
845 	 * turn on the owner execute bit of the lock file to disable printing.
846 	 */
847 	lock_file_name(pp, file, sizeof file);
848 	seteuid(euid);
849 	if (stat(file, &stbuf) >= 0) {
850 		if (chmod(file, stbuf.st_mode|LFM_PRINT_DIS|LFM_QUEUE_DIS) < 0)
851 			printf("\tcannot disable queuing: %s\n",
852 			       strerror(errno));
853 		else
854 			printf("\tprinter and queuing disabled\n");
855 	} else if (errno == ENOENT) {
856 		if ((fd = open(file, O_WRONLY|O_CREAT,
857 			       LOCK_FILE_MODE|LFM_PRINT_DIS|LFM_QUEUE_DIS)) < 0)
858 			printf("\tcannot create lock file: %s\n",
859 			       strerror(errno));
860 		else {
861 			(void) close(fd);
862 			printf("\tprinter and queuing disabled\n");
863 		}
864 		seteuid(uid);
865 		return;
866 	} else
867 		printf("\tcannot stat lock file\n");
868 	/*
869 	 * Write the message into the status file.
870 	 */
871 	status_file_name(pp, file, sizeof file);
872 	fd = open(file, O_WRONLY|O_CREAT|O_EXLOCK, STAT_FILE_MODE);
873 	if (fd < 0) {
874 		printf("\tcannot create status file: %s\n", strerror(errno));
875 		seteuid(uid);
876 		return;
877 	}
878 	seteuid(uid);
879 	(void) ftruncate(fd, 0);
880 	if (argc <= 0) {
881 		(void) write(fd, "\n", 1);
882 		(void) close(fd);
883 		return;
884 	}
885 	cp1 = buf;
886 	while (--argc >= 0) {
887 		cp2 = *argv++;
888 		while ((size_t)(cp1 - buf) < sizeof(buf) && (*cp1++ = *cp2++))
889 			;
890 		cp1[-1] = ' ';
891 	}
892 	cp1[-1] = '\n';
893 	*cp1 = '\0';
894 	(void) write(fd, buf, strlen(buf));
895 	(void) close(fd);
896 }
897 
898 /*
899  * Exit lpc
900  */
901 void
902 quit(int argc __unused, char *argv[] __unused)
903 {
904 	exit(0);
905 }
906 
907 /*
908  * Kill and restart the daemon.
909  */
910 void
911 restart(struct printer *pp)
912 {
913 	abortpr(pp, 0);
914 	startpr(pp, 0);
915 }
916 
917 /*
918  * Enable printing on the specified printer and startup the daemon.
919  */
920 void
921 startcmd(struct printer *pp)
922 {
923 	startpr(pp, 1);
924 }
925 
926 static void
927 startpr(struct printer *pp, int chgenable)
928 {
929 	struct stat stbuf;
930 	char lf[MAXPATHLEN];
931 
932 	lock_file_name(pp, lf, sizeof lf);
933 	printf("%s:\n", pp->printer);
934 
935 	/*
936 	 * For chgenable==1 ('start'), turn off the LFM_PRINT_DIS bit of the
937 	 * lock file to re-enable printing.  For chgenable==2 ('up'), also
938 	 * turn off the LFM_QUEUE_DIS bit to re-enable queueing.
939 	 */
940 	seteuid(euid);
941 	if (chgenable && stat(lf, &stbuf) >= 0) {
942 		mode_t bits = (chgenable == 2 ? 0 : LFM_QUEUE_DIS);
943 		if (chmod(lf, stbuf.st_mode & (LOCK_FILE_MODE | bits)) < 0)
944 			printf("\tcannot enable printing\n");
945 		else
946 			printf("\tprinting enabled\n");
947 	}
948 	if (!startdaemon(pp))
949 		printf("\tcouldn't start daemon\n");
950 	else
951 		printf("\tdaemon started\n");
952 	seteuid(uid);
953 }
954 
955 /*
956  * Print the status of the printer queue.
957  */
958 void
959 status(struct printer *pp)
960 {
961 	struct stat stbuf;
962 	register int fd, i;
963 	register struct dirent *dp;
964 	DIR *dirp;
965 	char file[MAXPATHLEN];
966 
967 	printf("%s:\n", pp->printer);
968 	lock_file_name(pp, file, sizeof file);
969 	if (stat(file, &stbuf) >= 0) {
970 		printf("\tqueuing is %s\n",
971 		       ((stbuf.st_mode & LFM_QUEUE_DIS) ? "disabled"
972 			: "enabled"));
973 		printf("\tprinting is %s\n",
974 		       ((stbuf.st_mode & LFM_PRINT_DIS) ? "disabled"
975 			: "enabled"));
976 	} else {
977 		printf("\tqueuing is enabled\n");
978 		printf("\tprinting is enabled\n");
979 	}
980 	if ((dirp = opendir(pp->spool_dir)) == NULL) {
981 		printf("\tcannot examine spool directory\n");
982 		return;
983 	}
984 	i = 0;
985 	while ((dp = readdir(dirp)) != NULL) {
986 		if (*dp->d_name == 'c' && dp->d_name[1] == 'f')
987 			i++;
988 	}
989 	closedir(dirp);
990 	if (i == 0)
991 		printf("\tno entries in spool area\n");
992 	else if (i == 1)
993 		printf("\t1 entry in spool area\n");
994 	else
995 		printf("\t%d entries in spool area\n", i);
996 	fd = open(file, O_RDONLY);
997 	if (fd < 0 || flock(fd, LOCK_SH|LOCK_NB) == 0) {
998 		(void) close(fd);	/* unlocks as well */
999 		printf("\tprinter idle\n");
1000 		return;
1001 	}
1002 	(void) close(fd);
1003 	/* print out the contents of the status file, if it exists */
1004 	status_file_name(pp, file, sizeof file);
1005 	fd = open(file, O_RDONLY|O_SHLOCK);
1006 	if (fd >= 0) {
1007 		(void) fstat(fd, &stbuf);
1008 		if (stbuf.st_size > 0) {
1009 			putchar('\t');
1010 			while ((i = read(fd, line, sizeof(line))) > 0)
1011 				(void) fwrite(line, 1, i, stdout);
1012 		}
1013 		(void) close(fd);	/* unlocks as well */
1014 	}
1015 }
1016 
1017 /*
1018  * Stop the specified daemon after completing the current job and disable
1019  * printing.
1020  */
1021 void
1022 stop(struct printer *pp)
1023 {
1024 	register int fd;
1025 	struct stat stbuf;
1026 	char lf[MAXPATHLEN];
1027 
1028 	lock_file_name(pp, lf, sizeof lf);
1029 	printf("%s:\n", pp->printer);
1030 
1031 	/*
1032 	 * Turn on the owner execute bit of the lock file to disable printing.
1033 	 */
1034 	seteuid(euid);
1035 	if (stat(lf, &stbuf) >= 0) {
1036 		if (chmod(lf, stbuf.st_mode | LFM_PRINT_DIS) < 0)
1037 			printf("\tcannot disable printing: %s\n",
1038 			       strerror(errno));
1039 		else {
1040 			upstat(pp, "printing disabled\n");
1041 			printf("\tprinting disabled\n");
1042 		}
1043 	} else if (errno == ENOENT) {
1044 		if ((fd = open(lf, O_WRONLY|O_CREAT,
1045 			       LOCK_FILE_MODE | LFM_PRINT_DIS)) < 0)
1046 			printf("\tcannot create lock file: %s\n",
1047 			       strerror(errno));
1048 		else {
1049 			(void) close(fd);
1050 			upstat(pp, "printing disabled\n");
1051 			printf("\tprinting disabled\n");
1052 		}
1053 	} else
1054 		printf("\tcannot stat lock file\n");
1055 	seteuid(uid);
1056 }
1057 
1058 struct	jobqueue **queue;
1059 int	nitems;
1060 time_t	mtime;
1061 
1062 /*
1063  * Put the specified jobs at the top of printer queue.
1064  */
1065 void
1066 topq(int argc, char *argv[])
1067 {
1068 	register int i;
1069 	struct stat stbuf;
1070 	int cmdstatus, changed;
1071 	struct printer myprinter, *pp = &myprinter;
1072 
1073 	if (argc < 3) {
1074 		printf("Usage: topq printer [jobnum ...] [user ...]\n");
1075 		return;
1076 	}
1077 
1078 	--argc;
1079 	++argv;
1080 	init_printer(pp);
1081 	cmdstatus = getprintcap(*argv, pp);
1082 	switch(cmdstatus) {
1083 	default:
1084 		fatal(pp, "%s", pcaperr(cmdstatus));
1085 	case PCAPERR_NOTFOUND:
1086 		printf("unknown printer %s\n", *argv);
1087 		return;
1088 	case PCAPERR_TCOPEN:
1089 		printf("warning: %s: unresolved tc= reference(s)", *argv);
1090 		break;
1091 	case PCAPERR_SUCCESS:
1092 		break;
1093 	}
1094 	printf("%s:\n", pp->printer);
1095 
1096 	seteuid(euid);
1097 	if (chdir(pp->spool_dir) < 0) {
1098 		printf("\tcannot chdir to %s\n", pp->spool_dir);
1099 		goto out;
1100 	}
1101 	seteuid(uid);
1102 	nitems = getq(pp, &queue);
1103 	if (nitems == 0)
1104 		return;
1105 	changed = 0;
1106 	mtime = queue[0]->job_time;
1107 	for (i = argc; --i; ) {
1108 		if (doarg(argv[i]) == 0) {
1109 			printf("\tjob %s is not in the queue\n", argv[i]);
1110 			continue;
1111 		} else
1112 			changed++;
1113 	}
1114 	for (i = 0; i < nitems; i++)
1115 		free(queue[i]);
1116 	free(queue);
1117 	if (!changed) {
1118 		printf("\tqueue order unchanged\n");
1119 		return;
1120 	}
1121 	/*
1122 	 * Turn on the public execute bit of the lock file to
1123 	 * get lpd to rebuild the queue after the current job.
1124 	 */
1125 	seteuid(euid);
1126 	if (changed && stat(pp->lock_file, &stbuf) >= 0)
1127 		(void) chmod(pp->lock_file, stbuf.st_mode | LFM_RESET_QUE);
1128 
1129 out:
1130 	seteuid(uid);
1131 }
1132 
1133 /*
1134  * Reposition the job by changing the modification time of
1135  * the control file.
1136  */
1137 static int
1138 touch(struct jobqueue *jq)
1139 {
1140 	struct timeval tvp[2];
1141 	int ret;
1142 
1143 	tvp[0].tv_sec = tvp[1].tv_sec = --mtime;
1144 	tvp[0].tv_usec = tvp[1].tv_usec = 0;
1145 	seteuid(euid);
1146 	ret = utimes(jq->job_cfname, tvp);
1147 	seteuid(uid);
1148 	return (ret);
1149 }
1150 
1151 /*
1152  * Checks if specified job name is in the printer's queue.
1153  * Returns:  negative (-1) if argument name is not in the queue.
1154  */
1155 static int
1156 doarg(char *job)
1157 {
1158 	register struct jobqueue **qq;
1159 	register int jobnum, n;
1160 	register char *cp, *machine;
1161 	int cnt = 0;
1162 	FILE *fp;
1163 
1164 	/*
1165 	 * Look for a job item consisting of system name, colon, number
1166 	 * (example: ucbarpa:114)
1167 	 */
1168 	if ((cp = strchr(job, ':')) != NULL) {
1169 		machine = job;
1170 		*cp++ = '\0';
1171 		job = cp;
1172 	} else
1173 		machine = NULL;
1174 
1175 	/*
1176 	 * Check for job specified by number (example: 112 or 235ucbarpa).
1177 	 */
1178 	if (isdigit(*job)) {
1179 		jobnum = 0;
1180 		do
1181 			jobnum = jobnum * 10 + (*job++ - '0');
1182 		while (isdigit(*job));
1183 		for (qq = queue + nitems; --qq >= queue; ) {
1184 			n = 0;
1185 			for (cp = (*qq)->job_cfname+3; isdigit(*cp); )
1186 				n = n * 10 + (*cp++ - '0');
1187 			if (jobnum != n)
1188 				continue;
1189 			if (*job && strcmp(job, cp) != 0)
1190 				continue;
1191 			if (machine != NULL && strcmp(machine, cp) != 0)
1192 				continue;
1193 			if (touch(*qq) == 0) {
1194 				printf("\tmoved %s\n", (*qq)->job_cfname);
1195 				cnt++;
1196 			}
1197 		}
1198 		return(cnt);
1199 	}
1200 	/*
1201 	 * Process item consisting of owner's name (example: henry).
1202 	 */
1203 	for (qq = queue + nitems; --qq >= queue; ) {
1204 		seteuid(euid);
1205 		fp = fopen((*qq)->job_cfname, "r");
1206 		seteuid(uid);
1207 		if (fp == NULL)
1208 			continue;
1209 		while (getline(fp) > 0)
1210 			if (line[0] == 'P')
1211 				break;
1212 		(void) fclose(fp);
1213 		if (line[0] != 'P' || strcmp(job, line+1) != 0)
1214 			continue;
1215 		if (touch(*qq) == 0) {
1216 			printf("\tmoved %s\n", (*qq)->job_cfname);
1217 			cnt++;
1218 		}
1219 	}
1220 	return(cnt);
1221 }
1222 
1223 /*
1224  * Enable everything and start printer (undo `down').
1225  */
1226 void
1227 up(struct printer *pp)
1228 {
1229 	startpr(pp, 2);
1230 }
1231