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