xref: /netbsd/usr.sbin/rpc.pcnfsd/pcnfsd_print.c (revision 7cb2935c)
1 /*	$NetBSD: pcnfsd_print.c,v 1.16 2020/04/22 23:46:02 joerg Exp $	*/
2 
3 /* RE_SID: @(%)/usr/dosnfs/shades_SCCS/unix/pcnfsd/v2/src/SCCS/s.pcnfsd_print.c 1.7 92/01/24 19:58:58 SMI */
4 /*
5 **=====================================================================
6 ** Copyright (c) 1986,1987,1988,1989,1990,1991 by Sun Microsystems, Inc.
7 **	@(#)pcnfsd_print.c	1.7	1/24/92
8 **=====================================================================
9 */
10 /*
11 **=====================================================================
12 **             I N C L U D E   F I L E   S E C T I O N                *
13 **                                                                    *
14 ** If your port requires different include files, add a suitable      *
15 ** #define in the customization section, and make the inclusion or    *
16 ** exclusion of the files conditional on this.                        *
17 **=====================================================================
18 */
19 
20 #include <sys/file.h>
21 #include <sys/ioctl.h>
22 #include <sys/stat.h>
23 
24 #include <ctype.h>
25 #include <errno.h>
26 #include <netdb.h>
27 #include <pwd.h>
28 #include <signal.h>
29 #include <stdio.h>
30 #include <stdlib.h>
31 #include <string.h>
32 #include <unistd.h>
33 
34 #ifndef SYSV
35 #include <sys/wait.h>
36 #endif
37 
38 #ifdef ISC_2_0
39 #include <sys/fcntl.h>
40 #endif
41 
42 #ifdef SHADOW_SUPPORT
43 #include <shadow.h>
44 #endif
45 
46 #include "paths.h"
47 
48 #include "common.h"
49 #include "pcnfsd.h"
50 #include "extern.h"
51 
52 /*
53 **---------------------------------------------------------------------
54 ** Other #define's
55 **---------------------------------------------------------------------
56 */
57 #ifndef MAXPATHLEN
58 #define MAXPATHLEN 1024
59 #endif
60 
61 /*
62 ** The following definitions give the maximum time allowed for
63 ** an external command to run (in seconds)
64 */
65 #define MAXTIME_FOR_PRINT	10
66 #define MAXTIME_FOR_QUEUE	10
67 #define MAXTIME_FOR_CANCEL	10
68 #define MAXTIME_FOR_STATUS	10
69 
70 #define QMAX 50
71 
72 /*
73 ** The following is derived from ucb/lpd/displayq.c
74 */
75 #define SIZECOL 62
76 #define FILECOL 24
77 
78 char   *expand_alias(char *, char *, char *, char *);
79 pr_list	list_virtual_printers(void);
80 char   *map_printer_name(char *);
81 void	substitute(char *, const char *, const char *);
82 int	suspicious(char *);
83 int	valid_pr(char *);
84 
85 /*
86 **---------------------------------------------------------------------
87 **                       Misc. variable definitions
88 **---------------------------------------------------------------------
89 */
90 
91 struct stat statbuf;
92 char    pathname[MAXPATHLEN];
93 char    new_pathname[MAXPATHLEN];
94 char    sp_name[MAXPATHLEN] = SPOOLDIR;
95 static char tempstr[256];
96 char    delims[] = " \t\r\n:()";
97 
98 pr_list printers = NULL;
99 pr_queue queue = NULL;
100 
101 /*
102 **=====================================================================
103 **                      C O D E   S E C T I O N                       *
104 **=====================================================================
105 */
106 
107 /*
108  * This is the latest word on the security check. The following
109  * routine "suspicious()" returns non-zero if the character string
110  * passed to it contains any shell metacharacters.
111  * Callers will typically code
112  *
113  *	if(suspicious(some_parameter)) reject();
114  */
115 
116 int
suspicious(char * s)117 suspicious(char *s)
118 {
119 	if (strpbrk(s, ";|&<>`'#!?*()[]^/${}\n\r\"\\:") != NULL)
120 		return 1;
121 	return 0;
122 }
123 
124 
125 int
valid_pr(char * pr)126 valid_pr(char *pr)
127 {
128 	char   *p;
129 	pr_list curr;
130 	if (printers == NULL)
131 		build_pr_list();
132 
133 	if (printers == NULL)
134 		return (1);	/* can't tell - assume it's good */
135 
136 	p = map_printer_name(pr);
137 	if (p == NULL)
138 		return (1);	/* must be ok is maps to NULL! */
139 	curr = printers;
140 	while (curr) {
141 		if (!strcmp(p, curr->pn))
142 			return (1);
143 		curr = curr->pr_next;
144 	}
145 
146 	return (0);
147 }
148 /*
149  * get pathname of current directory and return to client
150  *
151  * Note: This runs as root on behalf of a client request.
152  * As described in CERT advisory CA-96.08, be careful about
153  * doing a chmod on something that could be a symlink...
154  */
155 pirstat
pr_init(char * sys,char * pr,char ** sp)156 pr_init(char *sys, char *pr, char **sp)
157 {
158 	int     dir_mode = 0777;
159 	int     rc;
160 	mode_t  oldmask;
161 
162 	*sp = &pathname[0];
163 	pathname[0] = '\0';
164 
165 	if (suspicious(sys) || suspicious(pr))
166 		return (PI_RES_FAIL);
167 
168 	/*
169 	 * Make sure the server spool directory exists.
170 	 * Never create it here - the sysadmin does that.
171 	 */
172 	if (stat(sp_name, &statbuf) || !S_ISDIR(statbuf.st_mode))
173 		goto badspool;
174 
175 	/*
176 	 * Create the client spool directory if needed.
177 	 * Just do the mkdir call and ignore EEXIST.
178 	 * Mode of client directory should be 777.
179 	 */
180 	(void) snprintf(pathname, sizeof(pathname), "%s/%s", sp_name, sys);
181 	oldmask = umask(0);
182 	rc = mkdir(pathname, dir_mode);	/* DON'T ignore this return code */
183 	umask(oldmask);
184 	if ((rc < 0) && (errno != EEXIST))
185 		goto badspool;
186 
187 	/* By this point the client spool dir should exist. */
188 	if (stat(pathname, &statbuf) || !S_ISDIR(statbuf.st_mode)) {
189 		/* No spool directory... */
190 badspool:
191 		(void) snprintf(tempstr, sizeof(tempstr),
192 		    "rpc.pcnfsd: unable to set up spool directory %s\n",
193 		    pathname);
194 		msg_out(tempstr);
195 		pathname[0] = '\0';	/* null to tell client bad vibes */
196 		return (PI_RES_FAIL);
197 	}
198 	/* OK, we have a spool directory. */
199 	if (!valid_pr(pr)) {
200 		pathname[0] = '\0';	/* null to tell client bad vibes */
201 		return (PI_RES_NO_SUCH_PRINTER);
202 	}
203 	return (PI_RES_OK);
204 }
205 psrstat
pr_start2(char * sys,char * pr,char * user,char * fname,char * opts,char ** id)206 pr_start2(char *sys, char *pr, char *user, char *fname, char *opts, char **id)
207 {
208 	char    snum[20];
209 	static char req_id[256];
210 	char    cmdbuf[256];
211 	char    resbuf[256];
212 	FILE   *fd;
213 	int     i;
214 	char   *xcmd;
215 	int     failed = 0;
216 
217 #ifdef HACK_FOR_ROTATED_TRANSCRIPT
218 	char    scratch[512];
219 #endif
220 
221 
222 	if (suspicious(sys) ||
223 	    suspicious(pr) ||
224 	    suspicious(user) ||
225 	    suspicious(fname))
226 		return (PS_RES_FAIL);
227 
228 	(void) snprintf(pathname, sizeof(pathname), "%s/%s/%s", sp_name,
229 	    sys,
230 	    fname);
231 
232 	*id = &req_id[0];
233 	req_id[0] = '\0';
234 
235 	if (stat(pathname, &statbuf)) {
236 		/*
237                 **-----------------------------------------------------------------
238 	        ** We can't stat the file. Let's try appending '.spl' and
239 	        ** see if it's already in progress.
240                 **-----------------------------------------------------------------
241 	        */
242 
243 		(void) strlcat(pathname, ".spl", sizeof(pathname));
244 		if (stat(pathname, &statbuf)) {
245 			/*
246 	                **----------------------------------------------------------------
247 		        ** It really doesn't exist.
248 	                **----------------------------------------------------------------
249 		        */
250 
251 
252 			return (PS_RES_NO_FILE);
253 		}
254 		/*
255                 **-------------------------------------------------------------
256 	        ** It is already on the way.
257                 **-------------------------------------------------------------
258 	        */
259 
260 
261 		return (PS_RES_ALREADY);
262 	}
263 	if (statbuf.st_size == 0) {
264 		/*
265                 **-------------------------------------------------------------
266 	        ** Null file - don't print it, just kill it.
267                 **-------------------------------------------------------------
268 	        */
269 		(void) unlink(pathname);
270 
271 		return (PS_RES_NULL);
272 	}
273 	/*
274         **-------------------------------------------------------------
275         ** The file is real, has some data, and is not already going out.
276         ** We rename it by appending '.spl' and exec "lpr" to do the
277         ** actual work.
278         **-------------------------------------------------------------
279         */
280 	(void) strlcpy(new_pathname, pathname, sizeof(new_pathname));
281 	(void) strlcat(new_pathname, ".spl", sizeof(new_pathname));
282 
283 	/*
284         **-------------------------------------------------------------
285 	** See if the new filename exists so as not to overwrite it.
286         **-------------------------------------------------------------
287 	*/
288 
289 
290 	if (!stat(new_pathname, &statbuf)) {
291 		(void) strlcpy(new_pathname, pathname, sizeof(new_pathname)); /* rebuild a new name */
292 		(void) snprintf(snum, sizeof(snum), "%d", rand()); /* get some number */
293 		(void) strlcat(new_pathname, snum, 4);
294 		(void) strlcat(new_pathname, ".spl", sizeof(new_pathname)); /* new spool file */
295 	}
296 	if (rename(pathname, new_pathname)) {
297 		/*
298                 **---------------------------------------------------------------
299 	        ** Should never happen.
300                 **---------------------------------------------------------------
301                 */
302 		(void) snprintf(tempstr, sizeof(tempstr),
303 		    "rpc.pcnfsd: spool file rename (%s->%s) failed.\n",
304 		    pathname, new_pathname);
305 		msg_out(tempstr);
306 		return (PS_RES_FAIL);
307 	}
308 	if (*opts == 'd') {
309 		/*
310 		 **------------------------------------------------------
311 		 ** This is a Diablo print stream. Apply the ps630
312 		 ** filter with the appropriate arguments.
313 		 **------------------------------------------------------
314 		 */
315 #if 0				/* XXX: Temporary fix for CERT advisory
316 				 * CA-96.08 */
317 		(void) run_ps630(new_pathname, opts);
318 #else
319 		(void) snprintf(tempstr, sizeof(tempstr),
320 		    "rpc.pcnfsd: ps630 filter disabled for %s\n", pathname);
321 		msg_out(tempstr);
322 		return (PS_RES_FAIL);
323 #endif
324 	}
325 	/*
326 	** Try to match to an aliased printer
327 	*/
328 	xcmd = expand_alias(pr, new_pathname, user, sys);
329 	if (!xcmd) {
330 #ifdef	SVR4
331 		/*
332 			 * Use the copy option so we can remove the original
333 			 * spooled nfs file from the spool directory.
334 			 */
335 		snprintf(cmdbuf, sizeof(cmdbuf), "/usr/bin/lp -c -d%s %s",
336 		    pr, new_pathname);
337 #else				/* SVR4 */
338 		/* BSD way: lpr */
339 		snprintf(cmdbuf, sizeof(cmdbuf), "%s/lpr -P%s %s",
340 		    LPRDIR, pr, new_pathname);
341 #endif				/* SVR4 */
342 		xcmd = cmdbuf;
343 	}
344 	if ((fd = su_popen(user, xcmd, MAXTIME_FOR_PRINT)) == NULL) {
345 		msg_out("rpc.pcnfsd: su_popen failed");
346 		return (PS_RES_FAIL);
347 	}
348 	req_id[0] = '\0';	/* assume failure */
349 	while (fgets(resbuf, 255, fd) != NULL) {
350 		i = strlen(resbuf);
351 		if (i)
352 			resbuf[i - 1] = '\0';	/* trim NL */
353 		if (!strncmp(resbuf, "request id is ", 14))
354 			/* New - just the first word is needed */
355 			strlcpy(req_id, strtok(&resbuf[14], delims),
356 			    sizeof(req_id));
357 		else
358 			if (strembedded("disabled", resbuf))
359 				failed = 1;
360 	}
361 	if (su_pclose(fd) == 255)
362 		msg_out("rpc.pcnfsd: su_pclose alert");
363 	(void) unlink(new_pathname);
364 	return ((failed | interrupted) ? PS_RES_FAIL : PS_RES_OK);
365 }
366 /*
367  * build_pr_list: determine which printers are valid.
368  * on SVR4 use "lpstat -v"
369  * on BSD use "lpc status"
370  */
371 
372 #ifdef	SVR4
373 /*
374  * In SVR4 the command to determine which printers are
375  * valid is lpstat -v. The output is something like this:
376  *
377  * device for lp: /dev/lp0
378  * system for pcdslw: hinode
379  * system for bletch: hinode (as printer hisname)
380  *
381  * On SunOS using the SysV compatibility package, the output
382  * is more like:
383  *
384  * device for lp is /dev/lp0
385  * device for pcdslw is the remote printer pcdslw on hinode
386  * device for bletch is the remote printer hisname on hinode
387  *
388  * It is fairly simple to create logic that will handle either
389  * possibility:
390  */
391 int
build_pr_list()392 build_pr_list()
393 {
394 	pr_list last = NULL;
395 	pr_list curr = NULL;
396 	char    buff[256];
397 	FILE   *p;
398 	char   *cp;
399 	int     saw_system;
400 
401 	p = popen("lpstat -v", "r");
402 	if (p == NULL) {
403 		msg_out("rpc.pcnfsd: unable to popen() lp status");
404 		return (0);
405 	}
406 	while (fgets(buff, 255, p) != NULL) {
407 		cp = strtok(buff, delims);
408 		if (!cp)
409 			continue;
410 		if (!strcmp(cp, "device"))
411 			saw_system = 0;
412 		else
413 			if (!strcmp(cp, "system"))
414 				saw_system = 1;
415 			else
416 				continue;
417 		cp = strtok(NULL, delims);
418 		if (!cp || strcmp(cp, "for"))
419 			continue;
420 		cp = strtok(NULL, delims);
421 		if (!cp)
422 			continue;
423 		curr = (struct pr_list_item *)
424 		    grab(sizeof(struct pr_list_item));
425 
426 		curr->pn = strdup(cp);
427 		curr->device = NULL;
428 		curr->remhost = NULL;
429 		curr->cm = strdup("-");
430 		curr->pr_next = NULL;
431 
432 		cp = strtok(NULL, delims);
433 
434 		if (cp && !strcmp(cp, "is"))
435 			cp = strtok(NULL, delims);
436 
437 		if (!cp) {
438 			free_pr_list_item(curr);
439 			continue;
440 		}
441 		if (saw_system) {
442 			/* "system" OR "system (as printer pname)" */
443 			curr->remhost = strdup(cp);
444 			cp = strtok(NULL, delims);
445 			if (!cp) {
446 				/* simple format */
447 				curr->device = strdup(curr->pn);
448 			} else {
449 				/* "sys (as printer pname)" */
450 				if (strcmp(cp, "as")) {
451 					free_pr_list_item(curr);
452 					continue;
453 				}
454 				cp = strtok(NULL, delims);
455 				if (!cp || strcmp(cp, "printer")) {
456 					free_pr_list_item(curr);
457 					continue;
458 				}
459 				cp = strtok(NULL, delims);
460 				if (!cp) {
461 					free_pr_list_item(curr);
462 					continue;
463 				}
464 				curr->device = strdup(cp);
465 			}
466 		} else
467 			if (!strcmp(cp, "the")) {
468 				/* start of "the remote printer foo on bar" */
469 				cp = strtok(NULL, delims);
470 				if (!cp || strcmp(cp, "remote")) {
471 					free_pr_list_item(curr);
472 					continue;
473 				}
474 				cp = strtok(NULL, delims);
475 				if (!cp || strcmp(cp, "printer")) {
476 					free_pr_list_item(curr);
477 					continue;
478 				}
479 				cp = strtok(NULL, delims);
480 				if (!cp) {
481 					free_pr_list_item(curr);
482 					continue;
483 				}
484 				curr->device = strdup(cp);
485 				cp = strtok(NULL, delims);
486 				if (!cp || strcmp(cp, "on")) {
487 					free_pr_list_item(curr);
488 					continue;
489 				}
490 				cp = strtok(NULL, delims);
491 				if (!cp) {
492 					free_pr_list_item(curr);
493 					continue;
494 				}
495 				curr->remhost = strdup(cp);
496 			} else {
497 				/* the local name */
498 				curr->device = strdup(cp);
499 				curr->remhost = strdup("");
500 			}
501 
502 		if (last == NULL)
503 			printers = curr;
504 		else
505 			last->pr_next = curr;
506 		last = curr;
507 
508 	}
509 	(void) pclose(p);
510 
511 	/*
512 	 ** Now add on the virtual printers, if any
513 	 */
514 	if (last == NULL)
515 		printers = list_virtual_printers();
516 	else
517 		last->pr_next = list_virtual_printers();
518 
519 	return (1);
520 }
521 #else				/* SVR4 */
522 
523 /*
524  * BSD way: lpc stat
525  */
526 int
build_pr_list()527 build_pr_list()
528 {
529 	pr_list last = NULL;
530 	pr_list curr = NULL;
531 	char    buff[256];
532 	FILE   *p;
533 	char   *cp;
534 
535 	snprintf(buff, sizeof(buff), "%s/lpc status", LPCDIR);
536 	p = popen(buff, "r");
537 	if (p == NULL) {
538 		msg_out("rpc.pcnfsd: unable to popen lpc stat");
539 		return (0);
540 	}
541 	while (fgets(buff, 255, p) != NULL) {
542 		if (isspace((unsigned char)buff[0]))
543 			continue;
544 
545 		if ((cp = strtok(buff, delims)) == NULL)
546 			continue;
547 
548 		curr = (struct pr_list_item *)
549 		    grab(sizeof(struct pr_list_item));
550 
551 		/* XXX - Should distinguish remote printers. */
552 		curr->pn = strdup(cp);
553 		curr->device = strdup(cp);
554 		curr->remhost = strdup("");
555 		curr->cm = strdup("-");
556 		curr->pr_next = NULL;
557 
558 		if (last == NULL)
559 			printers = curr;
560 		else
561 			last->pr_next = curr;
562 		last = curr;
563 
564 	}
565 	(void) pclose(p);
566 
567 	/*
568 	 ** Now add on the virtual printers, if any
569 	 */
570 	if (last == NULL)
571 		printers = list_virtual_printers();
572 	else
573 		last->pr_next = list_virtual_printers();
574 
575 	return (1);
576 }
577 #endif				/* SVR4 */
578 
579 void   *
grab(int n)580 grab(int n)
581 {
582 	void   *p;
583 
584 	p = (void *) malloc(n);
585 	if (p == NULL) {
586 		msg_out("rpc.pcnfsd: malloc failure");
587 		exit(1);
588 	}
589 	return (p);
590 }
591 
592 void
free_pr_list_item(pr_list curr)593 free_pr_list_item(pr_list curr)
594 {
595 	if (curr->pn)
596 		free(curr->pn);
597 	if (curr->device)
598 		free(curr->device);
599 	if (curr->remhost)
600 		free(curr->remhost);
601 	if (curr->cm)
602 		free(curr->cm);
603 	if (curr->pr_next)
604 		free_pr_list_item(curr->pr_next);	/* recurse */
605 	free(curr);
606 }
607 /*
608  * build_pr_queue:  used to show the print queue.
609  *
610  * Note that the first thing we do is to discard any
611  * existing queue.
612  */
613 #ifdef SVR4
614 
615 /*
616 ** In SVR4 the command to list the print jobs for printer
617 ** lp is "lpstat lp" (or, equivalently, "lpstat -p lp").
618 ** The output looks like this:
619 **
620 ** lp-2                    root               939   Jul 10 21:56
621 ** lp-5                    geoff               15   Jul 12 23:23
622 ** lp-6                    geoff               15   Jul 12 23:23
623 **
624 ** If the first job is actually printing the first line
625 ** is modified, as follows:
626 **
627 ** lp-2                    root               939   Jul 10 21:56 on lp
628 **
629 ** I don't yet have any info on what it looks like if the printer
630 ** is remote and we're spooling over the net. However for
631 ** the purposes of rpc.pcnfsd we can simply say that field 1 is the
632 ** job ID, field 2 is the submitter, and field 3 is the size.
633 ** We can check for the presence of the string " on " in the
634 ** first record to determine if we should count it as rank 0 or rank 1,
635 ** but it won't hurt if we get it wrong.
636 **/
637 
638 pirstat
build_pr_queue(printername pn,username user,int just_mine,int p_qlen,int p_qshown)639 build_pr_queue(printername pn, username user, int just_mine, int p_qlen, int p_qshown)
640 {
641 	pr_queue last = NULL;
642 	pr_queue curr = NULL;
643 	char    buff[256];
644 	FILE   *p;
645 	char   *owner;
646 	char   *job;
647 	char   *totsize;
648 
649 	if (queue) {
650 		free_pr_queue_item(queue);
651 		queue = NULL;
652 	}
653 	*p_qlen = 0;
654 	*p_qshown = 0;
655 
656 	pn = map_printer_name(pn);
657 	if (pn == NULL || !valid_pr(pn) || suspicious(pn))
658 		return (PI_RES_NO_SUCH_PRINTER);
659 
660 	snprintf(buff, sizeof(buff), "/usr/bin/lpstat %s", pn);
661 	p = su_popen(user, buff, MAXTIME_FOR_QUEUE);
662 	if (p == NULL) {
663 		msg_out("rpc.pcnfsd: unable to popen() lpstat queue query");
664 		return (PI_RES_FAIL);
665 	}
666 	while (fgets(buff, 255, p) != NULL) {
667 		job = strtok(buff, delims);
668 		if (!job)
669 			continue;
670 
671 		owner = strtok(NULL, delims);
672 		if (!owner)
673 			continue;
674 
675 		totsize = strtok(NULL, delims);
676 		if (!totsize)
677 			continue;
678 
679 		*p_qlen += 1;
680 
681 		if (*p_qshown > QMAX)
682 			continue;
683 
684 		if (just_mine && strcasecmp(owner, user))
685 			continue;
686 
687 		*p_qshown += 1;
688 
689 		curr = (struct pr_queue_item *)
690 		    grab(sizeof(struct pr_queue_item));
691 
692 		curr->position = *p_qlen;
693 		curr->id = strdup(job);
694 		curr->size = strdup(totsize);
695 		curr->status = strdup("");
696 		curr->system = strdup("");
697 		curr->user = strdup(owner);
698 		curr->file = strdup("");
699 		curr->cm = strdup("-");
700 		curr->pr_next = NULL;
701 
702 		if (last == NULL)
703 			queue = curr;
704 		else
705 			last->pr_next = curr;
706 		last = curr;
707 
708 	}
709 	(void) su_pclose(p);
710 	return (PI_RES_OK);
711 }
712 #else				/* SVR4 */
713 
714 pirstat
build_pr_queue(printername pn,username user,int just_mine,int * p_qlen,int * p_qshown)715 build_pr_queue(printername pn, username user, int just_mine, int *p_qlen, int *p_qshown)
716 {
717 	pr_queue last = NULL;
718 	pr_queue curr = NULL;
719 	char    buff[256];
720 	FILE   *p;
721 	char   *cp;
722 	int     i;
723 	char   *rank;
724 	char   *owner;
725 	char   *job;
726 	char   *files;
727 	char   *totsize;
728 
729 	if (queue) {
730 		free_pr_queue_item(queue);
731 		queue = NULL;
732 	}
733 	*p_qlen = 0;
734 	*p_qshown = 0;
735 	pn = map_printer_name(pn);
736 	if (pn == NULL || suspicious(pn))
737 		return (PI_RES_NO_SUCH_PRINTER);
738 
739 	snprintf(buff, sizeof(buff), "%s/lpq -P%s", LPRDIR, pn);
740 
741 	p = su_popen(user, buff, MAXTIME_FOR_QUEUE);
742 	if (p == NULL) {
743 		msg_out("rpc.pcnfsd: unable to popen() lpq");
744 		return (PI_RES_FAIL);
745 	}
746 	while (fgets(buff, 255, p) != NULL) {
747 		i = strlen(buff) - 1;
748 		buff[i] = '\0';	/* zap trailing NL */
749 		if (i < SIZECOL)
750 			continue;
751 		if (!strncasecmp(buff, "rank", 4))
752 			continue;
753 
754 		totsize = &buff[SIZECOL - 1];
755 		files = &buff[FILECOL - 1];
756 		cp = totsize;
757 		cp--;
758 		while (cp > files && isspace((unsigned char)*cp))
759 			*cp-- = '\0';
760 
761 		buff[FILECOL - 2] = '\0';
762 
763 		cp = strtok(buff, delims);
764 		if (!cp)
765 			continue;
766 		rank = cp;
767 
768 		cp = strtok(NULL, delims);
769 		if (!cp)
770 			continue;
771 		owner = cp;
772 
773 		cp = strtok(NULL, delims);
774 		if (!cp)
775 			continue;
776 		job = cp;
777 
778 		*p_qlen += 1;
779 
780 		if (*p_qshown > QMAX)
781 			continue;
782 
783 		if (just_mine && strcasecmp(owner, user))
784 			continue;
785 
786 		*p_qshown += 1;
787 
788 		curr = (struct pr_queue_item *)
789 		    grab(sizeof(struct pr_queue_item));
790 
791 		curr->position = atoi(rank);	/* active -> 0 */
792 		curr->id = strdup(job);
793 		curr->size = strdup(totsize);
794 		curr->status = strdup(rank);
795 		curr->system = strdup("");
796 		curr->user = strdup(owner);
797 		curr->file = strdup(files);
798 		curr->cm = strdup("-");
799 		curr->pr_next = NULL;
800 
801 		if (last == NULL)
802 			queue = curr;
803 		else
804 			last->pr_next = curr;
805 		last = curr;
806 
807 	}
808 	(void) su_pclose(p);
809 	return (PI_RES_OK);
810 }
811 #endif				/* SVR4 */
812 
813 void
free_pr_queue_item(pr_queue curr)814 free_pr_queue_item(pr_queue curr)
815 {
816 	if (curr->id)
817 		free(curr->id);
818 	if (curr->size)
819 		free(curr->size);
820 	if (curr->status)
821 		free(curr->status);
822 	if (curr->system)
823 		free(curr->system);
824 	if (curr->user)
825 		free(curr->user);
826 	if (curr->file)
827 		free(curr->file);
828 	if (curr->cm)
829 		free(curr->cm);
830 	if (curr->pr_next)
831 		free_pr_queue_item(curr->pr_next);	/* recurse */
832 	free(curr);
833 }
834 #ifdef SVR4
835 
836 /*
837 ** New - SVR4 printer status handling.
838 **
839 ** The command we'll use for checking the status of printer "lp"
840 ** is "lpstat -a lp -p lp". Here are some sample outputs:
841 **
842 **
843 ** lp accepting requests since Wed Jul 10 21:49:25 EDT 1991
844 ** printer lp disabled since Thu Feb 21 22:52:36 EST 1991. available.
845 ** 	new printer
846 ** ---
847 ** pcdslw not accepting requests since Fri Jul 12 22:30:00 EDT 1991 -
848 ** 	unknown reason
849 ** printer pcdslw disabled since Fri Jul 12 22:15:37 EDT 1991. available.
850 ** 	new printer
851 ** ---
852 ** lp accepting requests since Wed Jul 10 21:49:25 EDT 1991
853 ** printer lp now printing lp-2. enabled since Sat Jul 13 12:02:17 EDT 1991. available.
854 ** ---
855 ** lp accepting requests since Wed Jul 10 21:49:25 EDT 1991
856 ** printer lp now printing lp-2. enabled since Sat Jul 13 12:02:17 EDT 1991. available.
857 ** ---
858 ** lp accepting requests since Wed Jul 10 21:49:25 EDT 1991
859 ** printer lp disabled since Sat Jul 13 12:05:20 EDT 1991. available.
860 ** 	unknown reason
861 ** ---
862 ** pcdslw not accepting requests since Fri Jul 12 22:30:00 EDT 1991 -
863 ** 	unknown reason
864 ** printer pcdslw is idle. enabled since Sat Jul 13 12:05:28 EDT 1991. available.
865 **
866 ** Note that these are actual outputs. The format (which is totally
867 ** different from the lpstat in SunOS) seems to break down as
868 ** follows:
869 ** (1) The first line has the form "printername [not] accepting requests,,,"
870 **    This is trivial to decode.
871 ** (2) The second line has several forms, all beginning "printer printername":
872 ** (2.1) "... disabled"
873 ** (2.2) "... is idle"
874 ** (2.3) "... now printing jobid"
875 ** The "available" comment seems to be meaningless. The next line
876 ** is the "reason" code which the operator can supply when issuing
877 ** a "disable" or "reject" command.
878 ** Note that there is no way to check the number of entries in the
879 ** queue except to ask for the queue and count them.
880 */
881 
882 pirstat
get_pr_status(printername pn,bool_t * avail,bool_t * printing,int * qlen,bool_t * needs_operator,char * status,size_t statuslen)883 get_pr_status(printername pn, bool_t *avail, bool_t *printing, int *qlen, bool_t *needs_operator, char *status, size_t statuslen)
884 {
885 	char    buff[256];
886 	char    cmd[64];
887 	FILE   *p;
888 	int     n;
889 	pirstat stat = PI_RES_NO_SUCH_PRINTER;
890 
891 	/* assume the worst */
892 	*avail = FALSE;
893 	*printing = FALSE;
894 	*needs_operator = FALSE;
895 	*qlen = 0;
896 	*status = '\0';
897 
898 	pn = map_printer_name(pn);
899 	if (pn == NULL || !valid_pr(pn) || suspicious(pn))
900 		return (PI_RES_NO_SUCH_PRINTER);
901 	n = strlen(pn);
902 
903 	snprintf(cmd, sizeof(cmd), "/usr/bin/lpstat -a %s -p %s", pn, pn);
904 
905 	p = popen(cmd, "r");
906 	if (p == NULL) {
907 		msg_out("rpc.pcnfsd: unable to popen() lp status");
908 		return (PI_RES_FAIL);
909 	}
910 	stat = PI_RES_OK;
911 
912 	while (fgets(buff, 255, p) != NULL) {
913 		if (!strncmp(buff, pn, n)) {
914 			if (!strstr(buff, "not accepting"))
915 				*avail = TRUE;
916 			continue;
917 		}
918 		if (!strncmp(buff, "printer ", 8)) {
919 			if (!strstr(buff, "disabled"))
920 				*printing = TRUE;
921 			if (strstr(buff, "printing"))
922 				strlcpy(status, "printing", statuslen);
923 			else
924 				if (strstr(buff, "idle"))
925 					strlcpy(status, "idle", statuslen);
926 			continue;
927 		}
928 		if (!strncmp(buff, "UX:", 3)) {
929 			stat = PI_RES_NO_SUCH_PRINTER;
930 		}
931 	}
932 	(void) pclose(p);
933 	return (stat);
934 }
935 #else				/* SVR4 */
936 
937 /*
938  * BSD way: lpc status
939  */
940 pirstat
get_pr_status(printername pn,bool_t * avail,bool_t * printing,int * qlen,bool_t * needs_operator,char * status,size_t statuslen)941 get_pr_status(printername pn, bool_t *avail, bool_t *printing, int *qlen, bool_t *needs_operator, char *status, size_t statuslen)
942 {
943 	char    cmd[128];
944 	char    buff[256];
945 	char    buff2[256];
946 	char    pname[64];
947 	FILE   *p;
948 	char   *cp;
949 	char   *cp1;
950 	char   *cp2;
951 	int     n;
952 	pirstat pstat = PI_RES_NO_SUCH_PRINTER;
953 
954 	/* assume the worst */
955 	*avail = FALSE;
956 	*printing = FALSE;
957 	*needs_operator = FALSE;
958 	*qlen = 0;
959 	*status = '\0';
960 
961 	pn = map_printer_name(pn);
962 	if (pn == NULL || suspicious(pn))
963 		return (PI_RES_NO_SUCH_PRINTER);
964 
965 	snprintf(pname, sizeof(pname), "%s:", pn);
966 	n = strlen(pname);
967 
968 	snprintf(cmd, sizeof(cmd), "%s/lpc status %s", LPCDIR, pn);
969 	p = popen(cmd, "r");
970 	if (p == NULL) {
971 		msg_out("rpc.pcnfsd: unable to popen() lp status");
972 		return (PI_RES_FAIL);
973 	}
974 	while (fgets(buff, 255, p) != NULL) {
975 		if (strncmp(buff, pname, n))
976 			continue;
977 /*
978 ** We have a match. The only failure now is PI_RES_FAIL if
979 ** lpstat output cannot be decoded
980 */
981 		pstat = PI_RES_FAIL;
982 /*
983 ** The next four lines are usually if the form
984 **
985 **     queuing is [enabled|disabled]
986 **     printing is [enabled|disabled]
987 **     [no entries | N entr[y|ies] in spool area]
988 **     <status message, may include the word "attention">
989 */
990 		while (fgets(buff, 255, p) != NULL && isspace((unsigned char)buff[0])) {
991 			cp = buff;
992 			while (isspace((unsigned char)*cp))
993 				cp++;
994 			if (*cp == '\0')
995 				break;
996 			cp1 = cp;
997 			cp2 = buff2;
998 			while (*cp1 && *cp1 != '\n') {
999 				*cp2++ = tolower((unsigned char)*cp1);
1000 				cp1++;
1001 			}
1002 			*cp1 = '\0';
1003 			*cp2 = '\0';
1004 /*
1005 ** Now buff2 has a lower-cased copy and cp points at the original;
1006 ** both are null terminated without any newline
1007 */
1008 			if (!strncmp(buff2, "queuing", 7)) {
1009 				*avail = (strstr(buff2, "enabled") != NULL);
1010 				continue;
1011 			}
1012 			if (!strncmp(buff2, "printing", 8)) {
1013 				*printing = (strstr(buff2, "enabled") != NULL);
1014 				continue;
1015 			}
1016 			if (isdigit((unsigned char)buff2[0]) && (strstr(buff2, "entr") != NULL)) {
1017 
1018 				*qlen = atoi(buff2);
1019 				continue;
1020 			}
1021 			if (strstr(buff2, "attention") != NULL ||
1022 			    strstr(buff2, "error") != NULL)
1023 				*needs_operator = TRUE;
1024 			if (*needs_operator || strstr(buff2, "waiting") != NULL)
1025 				strlcpy(status, cp, statuslen);
1026 		}
1027 		pstat = PI_RES_OK;
1028 		break;
1029 	}
1030 	(void) pclose(p);
1031 	return (pstat);
1032 }
1033 #endif				/* SVR4 */
1034 
1035 /*
1036  * pr_cancel: cancel a print job
1037  */
1038 #ifdef SVR4
1039 
1040 /*
1041 ** For SVR4 we have to be prepared for the following kinds of output:
1042 **
1043 ** # cancel lp-6
1044 ** request "lp-6" cancelled
1045 ** # cancel lp-33
1046 ** UX:cancel: WARNING: Request "lp-33" doesn't exist.
1047 ** # cancel foo-88
1048 ** UX:cancel: WARNING: Request "foo-88" doesn't exist.
1049 ** # cancel foo
1050 ** UX:cancel: WARNING: "foo" is not a request id or a printer.
1051 **             TO FIX: Cancel requests by id or by
1052 **                     name of printer where printing.
1053 ** # su geoff
1054 ** $ cancel lp-2
1055 ** UX:cancel: WARNING: Can't cancel request "lp-2".
1056 **             TO FIX: You are not allowed to cancel
1057 **                     another's request.
1058 **
1059 ** There are probably other variations for remote printers.
1060 ** Basically, if the reply begins with the string
1061 **          "UX:cancel: WARNING: "
1062 ** we can strip this off and look for one of the following
1063 ** (1) 'R' - should be part of "Request "xxxx" doesn't exist."
1064 ** (2) '"' - should be start of ""foo" is not a request id or..."
1065 ** (3) 'C' - should be start of "Can't cancel request..."
1066 **
1067 ** The fly in the ointment: all of this can change if these
1068 ** messages are localized..... :-(
1069 */
1070 pcrstat
pr_cancel(char * pr,char * user,char * id)1071 pr_cancel(char *pr, char *user, char *id)
1072 {
1073 	char    cmdbuf[256];
1074 	char    resbuf[256];
1075 	FILE   *fd;
1076 	pcrstat stat = PC_RES_NO_SUCH_JOB;
1077 
1078 	pr = map_printer_name(pr);
1079 	if (pr == NULL || suspicious(pr))
1080 		return (PC_RES_NO_SUCH_PRINTER);
1081 	if (suspicious(id))
1082 		return (PC_RES_NO_SUCH_JOB);
1083 
1084 	snprintf(cmdbuf, sizeof(cmdbuf), "/usr/bin/cancel %s", id);
1085 	if ((fd = su_popen(user, cmdbuf, MAXTIME_FOR_CANCEL)) == NULL) {
1086 		msg_out("rpc.pcnfsd: su_popen failed");
1087 		return (PC_RES_FAIL);
1088 	}
1089 	if (fgets(resbuf, 255, fd) == NULL)
1090 		stat = PC_RES_FAIL;
1091 	else
1092 		if (!strstr(resbuf, "UX:"))
1093 			stat = PC_RES_OK;
1094 		else
1095 			if (strstr(resbuf, "doesn't exist"))
1096 				stat = PC_RES_NO_SUCH_JOB;
1097 			else
1098 				if (strstr(resbuf, "not a request id"))
1099 					stat = PC_RES_NO_SUCH_JOB;
1100 				else
1101 					if (strstr(resbuf, "Can't cancel request"))
1102 						stat = PC_RES_NOT_OWNER;
1103 					else
1104 						stat = PC_RES_FAIL;
1105 
1106 	if (su_pclose(fd) == 255)
1107 		msg_out("rpc.pcnfsd: su_pclose alert");
1108 	return (stat);
1109 }
1110 #else				/* SVR4 */
1111 
1112 /*
1113  * BSD way: lprm
1114  */
1115 pcrstat
pr_cancel(char * pr,char * user,char * id)1116 pr_cancel(char *pr, char *user, char *id)
1117 {
1118 	char    cmdbuf[256];
1119 	char    resbuf[256];
1120 	FILE   *fd;
1121 	int     i;
1122 	pcrstat pstat = PC_RES_NO_SUCH_JOB;
1123 
1124 	pr = map_printer_name(pr);
1125 	if (pr == NULL || suspicious(pr))
1126 		return (PC_RES_NO_SUCH_PRINTER);
1127 	if (suspicious(id))
1128 		return (PC_RES_NO_SUCH_JOB);
1129 
1130 	snprintf(cmdbuf, sizeof(cmdbuf), "%s/lprm -P%s %s", LPRDIR, pr, id);
1131 	if ((fd = su_popen(user, cmdbuf, MAXTIME_FOR_CANCEL)) == NULL) {
1132 		msg_out("rpc.pcnfsd: su_popen failed");
1133 		return (PC_RES_FAIL);
1134 	}
1135 	while (fgets(resbuf, 255, fd) != NULL) {
1136 		i = strlen(resbuf);
1137 		if (i)
1138 			resbuf[i - 1] = '\0';	/* trim NL */
1139 		if (strstr(resbuf, "dequeued") != NULL)
1140 			pstat = PC_RES_OK;
1141 		if (strstr(resbuf, "unknown printer") != NULL)
1142 			pstat = PC_RES_NO_SUCH_PRINTER;
1143 		if (strstr(resbuf, "Permission denied") != NULL)
1144 			pstat = PC_RES_NOT_OWNER;
1145 	}
1146 	if (su_pclose(fd) == 255)
1147 		msg_out("rpc.pcnfsd: su_pclose alert");
1148 	return (pstat);
1149 }
1150 #endif				/* SVR4 */
1151 
1152 /*
1153 ** New subsystem here. We allow the administrator to define
1154 ** up to NPRINTERDEFS aliases for printer names. This is done
1155 ** using the "/etc/pcnfsd.conf" file, which is read at startup.
1156 ** There are three entry points to this subsystem
1157 **
1158 ** void add_printer_alias(char *printer, char *alias_for, char *command)
1159 **
1160 ** This is invoked from "config_from_file()" for each
1161 ** "printer" line. "printer" is the name of a printer; note that
1162 ** it is possible to redefine an existing printer. "alias_for"
1163 ** is the name of the underlying printer, used for queue listing
1164 ** and other control functions. If it is "-", there is no
1165 ** underlying printer, or the administrative functions are
1166 ** not applicable to this printer. "command"
1167 ** is the command which should be run (via "su_popen()") if a
1168 ** job is printed on this printer. The following tokens may be
1169 ** embedded in the command, and are substituted as follows:
1170 **
1171 ** $FILE	-	path to the file containing the print data
1172 ** $USER	-	login of user
1173 ** $HOST	-	hostname from which job originated
1174 **
1175 ** Tokens may occur multiple times. If The command includes no
1176 ** $FILE token, the string " $FILE" is silently appended.
1177 **
1178 ** pr_list list_virtual_printers()
1179 **
1180 ** This is invoked from build_pr_list to generate a list of aliased
1181 ** printers, so that the client that asks for a list of valid printers
1182 ** will see these ones.
1183 **
1184 ** char *map_printer_name(char *printer)
1185 **
1186 ** If "printer" identifies an aliased printer, this function returns
1187 ** the "alias_for" name, or NULL if the "alias_for" was given as "-".
1188 ** Otherwise it returns its argument.
1189 **
1190 ** char *expand_alias(char *printer, char *file, char *user, char *host)
1191 **
1192 ** If "printer" is an aliased printer, this function returns a
1193 ** pointer to a static string in which the corresponding command
1194 ** has been expanded. Otherwise ot returns NULL.
1195 */
1196 #define NPRINTERDEFS	16
1197 int     num_aliases = 0;
1198 struct {
1199 	char   *a_printer;
1200 	char   *a_alias_for;
1201 	char   *a_command;
1202 }       alias[NPRINTERDEFS];
1203 
1204 void
add_printer_alias(char * printer,char * alias_for,char * command)1205 add_printer_alias(char *printer, char *alias_for, char *command)
1206 {
1207 	size_t l;
1208 
1209 	if (num_aliases < NPRINTERDEFS) {
1210 		alias[num_aliases].a_printer = strdup(printer);
1211 		alias[num_aliases].a_alias_for =
1212 		    (strcmp(alias_for, "-") ? strdup(alias_for) : NULL);
1213 		if (strstr(command, "$FILE"))
1214 			alias[num_aliases].a_command = strdup(command);
1215 		else {
1216 			l = strlen(command) + 8;
1217 			alias[num_aliases].a_command = (char *) grab(l);
1218 			strlcpy(alias[num_aliases].a_command, command, l);
1219 			strlcat(alias[num_aliases].a_command, " $FILE", l);
1220 		}
1221 		num_aliases++;
1222 	}
1223 }
1224 
1225 pr_list
list_virtual_printers()1226 list_virtual_printers()
1227 {
1228 	pr_list first = NULL;
1229 	pr_list last = NULL;
1230 	pr_list curr = NULL;
1231 	int     i;
1232 
1233 
1234 	if (num_aliases == 0)
1235 		return (NULL);
1236 
1237 	for (i = 0; i < num_aliases; i++) {
1238 		curr = (struct pr_list_item *)
1239 		    grab(sizeof(struct pr_list_item));
1240 
1241 		curr->pn = strdup(alias[i].a_printer);
1242 		if (alias[i].a_alias_for == NULL)
1243 			curr->device = strdup("");
1244 		else
1245 			curr->device = strdup(alias[i].a_alias_for);
1246 		curr->remhost = strdup("");
1247 		curr->cm = strdup("(alias)");
1248 		curr->pr_next = NULL;
1249 		if (last == NULL)
1250 			first = curr;
1251 		else
1252 			last->pr_next = curr;
1253 		last = curr;
1254 
1255 	}
1256 	return (first);
1257 }
1258 
1259 
1260 char   *
map_printer_name(char * printer)1261 map_printer_name(char *printer)
1262 {
1263 	int     i;
1264 	for (i = 0; i < num_aliases; i++) {
1265 		if (!strcmp(printer, alias[i].a_printer))
1266 			return (alias[i].a_alias_for);
1267 	}
1268 	return (printer);
1269 }
1270 
1271 void
substitute(char * string,const char * token,const char * data)1272 substitute(char *string, const char *token, const char *data)
1273 {
1274 	char    temp[512];
1275 	char   *c;
1276 
1277 	while ((c = strstr(string, token)) != NULL) {
1278 		*c = '\0';
1279 		strlcpy(temp, string, sizeof(temp));
1280 		strlcat(temp, data, sizeof(temp));
1281 		c += strlen(token);
1282 		strlcat(temp, c, sizeof(temp));
1283 		strcpy(string, temp);
1284 	}
1285 }
1286 
1287 char   *
expand_alias(char * printer,char * file,char * user,char * host)1288 expand_alias(char *printer, char *file, char *user, char *host)
1289 {
1290 	static char expansion[512];
1291 	int     i;
1292 	for (i = 0; i < num_aliases; i++) {
1293 		if (!strcmp(printer, alias[i].a_printer)) {
1294 			strlcpy(expansion, alias[i].a_command,
1295 			    sizeof(expansion));
1296 			substitute(expansion, "$FILE", file);
1297 			substitute(expansion, "$USER", user);
1298 			substitute(expansion, "$HOST", host);
1299 			return (expansion);
1300 		}
1301 	}
1302 	return (NULL);
1303 }
1304