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