pcnfsd_print.c revision 1.7
1/*	$NetBSD: pcnfsd_print.c,v 1.7 2003/07/16 08:22:01 itojun 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 *, char *, 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(system, pr, user, fname, opts, id)
212	char   *system;
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(system) ||
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	    system,
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, system);
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';	/* asume 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(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) fclose(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(*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)
908	printername pn;
909	bool_t *avail;
910	bool_t *printing;
911	int    *qlen;
912	bool_t *needs_operator;
913	char   *status;
914{
915	char    buff[256];
916	char    cmd[64];
917	FILE   *p;
918	int     n;
919	pirstat stat = PI_RES_NO_SUCH_PRINTER;
920
921	/* assume the worst */
922	*avail = FALSE;
923	*printing = FALSE;
924	*needs_operator = FALSE;
925	*qlen = 0;
926	*status = '\0';
927
928	pn = map_printer_name(pn);
929	if (pn == NULL || !valid_pr(pn) || suspicious(pn))
930		return (PI_RES_NO_SUCH_PRINTER);
931	n = strlen(pn);
932
933	snprintf(cmd, sizeof(cmd), "/usr/bin/lpstat -a %s -p %s", pn, pn);
934
935	p = popen(cmd, "r");
936	if (p == NULL) {
937		msg_out("rpc.pcnfsd: unable to popen() lp status");
938		return (PI_RES_FAIL);
939	}
940	stat = PI_RES_OK;
941
942	while (fgets(buff, 255, p) != NULL) {
943		if (!strncmp(buff, pn, n)) {
944			if (!strstr(buff, "not accepting"))
945				*avail = TRUE;
946			continue;
947		}
948		if (!strncmp(buff, "printer ", 8)) {
949			if (!strstr(buff, "disabled"))
950				*printing = TRUE;
951			if (strstr(buff, "printing"))
952				strlcpy(status, "printing", sizeof9status));
953			else
954				if (strstr(buff, "idle"))
955					strlcpy(status, "idle", sizeof(status));
956			continue;
957		}
958		if (!strncmp(buff, "UX:", 3)) {
959			stat = PI_RES_NO_SUCH_PRINTER;
960		}
961	}
962	(void) pclose(p);
963	return (stat);
964}
965#else				/* SVR4 */
966
967/*
968 * BSD way: lpc status
969 */
970pirstat
971get_pr_status(pn, avail, printing, qlen, needs_operator, status)
972	printername pn;
973	bool_t *avail;
974	bool_t *printing;
975	int    *qlen;
976	bool_t *needs_operator;
977	char   *status;
978{
979	char    cmd[128];
980	char    buff[256];
981	char    buff2[256];
982	char    pname[64];
983	FILE   *p;
984	char   *cp;
985	char   *cp1;
986	char   *cp2;
987	int     n;
988	pirstat stat = PI_RES_NO_SUCH_PRINTER;
989
990	/* assume the worst */
991	*avail = FALSE;
992	*printing = FALSE;
993	*needs_operator = FALSE;
994	*qlen = 0;
995	*status = '\0';
996
997	pn = map_printer_name(pn);
998	if (pn == NULL || suspicious(pn))
999		return (PI_RES_NO_SUCH_PRINTER);
1000
1001	snprintf(pname, sizeof(pname), "%s:", pn);
1002	n = strlen(pname);
1003
1004	snprintf(cmd, sizeof(cmd), "%s/lpc status %s", LPCDIR, pn);
1005	p = popen(cmd, "r");
1006	if (p == NULL) {
1007		msg_out("rpc.pcnfsd: unable to popen() lp status");
1008		return (PI_RES_FAIL);
1009	}
1010	while (fgets(buff, 255, p) != NULL) {
1011		if (strncmp(buff, pname, n))
1012			continue;
1013/*
1014** We have a match. The only failure now is PI_RES_FAIL if
1015** lpstat output cannot be decoded
1016*/
1017		stat = PI_RES_FAIL;
1018/*
1019** The next four lines are usually if the form
1020**
1021**     queuing is [enabled|disabled]
1022**     printing is [enabled|disabled]
1023**     [no entries | N entr[y|ies] in spool area]
1024**     <status message, may include the word "attention">
1025*/
1026		while (fgets(buff, 255, p) != NULL && isspace(buff[0])) {
1027			cp = buff;
1028			while (isspace(*cp))
1029				cp++;
1030			if (*cp == '\0')
1031				break;
1032			cp1 = cp;
1033			cp2 = buff2;
1034			while (*cp1 && *cp1 != '\n') {
1035				*cp2++ = tolower(*cp1);
1036				cp1++;
1037			}
1038			*cp1 = '\0';
1039			*cp2 = '\0';
1040/*
1041** Now buff2 has a lower-cased copy and cp points at the original;
1042** both are null terminated without any newline
1043*/
1044			if (!strncmp(buff2, "queuing", 7)) {
1045				*avail = (strstr(buff2, "enabled") != NULL);
1046				continue;
1047			}
1048			if (!strncmp(buff2, "printing", 8)) {
1049				*printing = (strstr(buff2, "enabled") != NULL);
1050				continue;
1051			}
1052			if (isdigit(buff2[0]) && (strstr(buff2, "entr") != NULL)) {
1053
1054				*qlen = atoi(buff2);
1055				continue;
1056			}
1057			if (strstr(buff2, "attention") != NULL ||
1058			    strstr(buff2, "error") != NULL)
1059				*needs_operator = TRUE;
1060			if (*needs_operator || strstr(buff2, "waiting") != NULL)
1061				strlcpy(status, cp, sizeof(status));
1062		}
1063		stat = PI_RES_OK;
1064		break;
1065	}
1066	(void) pclose(p);
1067	return (stat);
1068}
1069#endif				/* SVR4 */
1070
1071/*
1072 * pr_cancel: cancel a print job
1073 */
1074#ifdef SVR4
1075
1076/*
1077** For SVR4 we have to be prepared for the following kinds of output:
1078**
1079** # cancel lp-6
1080** request "lp-6" cancelled
1081** # cancel lp-33
1082** UX:cancel: WARNING: Request "lp-33" doesn't exist.
1083** # cancel foo-88
1084** UX:cancel: WARNING: Request "foo-88" doesn't exist.
1085** # cancel foo
1086** UX:cancel: WARNING: "foo" is not a request id or a printer.
1087**             TO FIX: Cancel requests by id or by
1088**                     name of printer where printing.
1089** # su geoff
1090** $ cancel lp-2
1091** UX:cancel: WARNING: Can't cancel request "lp-2".
1092**             TO FIX: You are not allowed to cancel
1093**                     another's request.
1094**
1095** There are probably other variations for remote printers.
1096** Basically, if the reply begins with the string
1097**          "UX:cancel: WARNING: "
1098** we can strip this off and look for one of the following
1099** (1) 'R' - should be part of "Request "xxxx" doesn't exist."
1100** (2) '"' - should be start of ""foo" is not a request id or..."
1101** (3) 'C' - should be start of "Can't cancel request..."
1102**
1103** The fly in the ointment: all of this can change if these
1104** messages are localized..... :-(
1105*/
1106pcrstat
1107pr_cancel(pr, user, id)
1108	char   *pr;
1109	char   *user;
1110	char   *id;
1111{
1112	char    cmdbuf[256];
1113	char    resbuf[256];
1114	FILE   *fd;
1115	pcrstat stat = PC_RES_NO_SUCH_JOB;
1116
1117	pr = map_printer_name(pr);
1118	if (pr == NULL || suspicious(pr))
1119		return (PC_RES_NO_SUCH_PRINTER);
1120	if (suspicious(id))
1121		return (PC_RES_NO_SUCH_JOB);
1122
1123	snprintf(cmdbuf, sizeof(cmdbuf), "/usr/bin/cancel %s", id);
1124	if ((fd = su_popen(user, cmdbuf, MAXTIME_FOR_CANCEL)) == NULL) {
1125		msg_out("rpc.pcnfsd: su_popen failed");
1126		return (PC_RES_FAIL);
1127	}
1128	if (fgets(resbuf, 255, fd) == NULL)
1129		stat = PC_RES_FAIL;
1130	else
1131		if (!strstr(resbuf, "UX:"))
1132			stat = PC_RES_OK;
1133		else
1134			if (strstr(resbuf, "doesn't exist"))
1135				stat = PC_RES_NO_SUCH_JOB;
1136			else
1137				if (strstr(resbuf, "not a request id"))
1138					stat = PC_RES_NO_SUCH_JOB;
1139				else
1140					if (strstr(resbuf, "Can't cancel request"))
1141						stat = PC_RES_NOT_OWNER;
1142					else
1143						stat = PC_RES_FAIL;
1144
1145	if (su_pclose(fd) == 255)
1146		msg_out("rpc.pcnfsd: su_pclose alert");
1147	return (stat);
1148}
1149#else				/* SVR4 */
1150
1151/*
1152 * BSD way: lprm
1153 */
1154pcrstat
1155pr_cancel(pr, user, id)
1156	char   *pr;
1157	char   *user;
1158	char   *id;
1159{
1160	char    cmdbuf[256];
1161	char    resbuf[256];
1162	FILE   *fd;
1163	int     i;
1164	pcrstat stat = PC_RES_NO_SUCH_JOB;
1165
1166	pr = map_printer_name(pr);
1167	if (pr == NULL || suspicious(pr))
1168		return (PC_RES_NO_SUCH_PRINTER);
1169	if (suspicious(id))
1170		return (PC_RES_NO_SUCH_JOB);
1171
1172	snprintf(cmdbuf, sizeof(cmdbuf), "%s/lprm -P%s %s", LPRDIR, pr, id);
1173	if ((fd = su_popen(user, cmdbuf, MAXTIME_FOR_CANCEL)) == NULL) {
1174		msg_out("rpc.pcnfsd: su_popen failed");
1175		return (PC_RES_FAIL);
1176	}
1177	while (fgets(resbuf, 255, fd) != NULL) {
1178		i = strlen(resbuf);
1179		if (i)
1180			resbuf[i - 1] = '\0';	/* trim NL */
1181		if (strstr(resbuf, "dequeued") != NULL)
1182			stat = PC_RES_OK;
1183		if (strstr(resbuf, "unknown printer") != NULL)
1184			stat = PC_RES_NO_SUCH_PRINTER;
1185		if (strstr(resbuf, "Permission denied") != NULL)
1186			stat = PC_RES_NOT_OWNER;
1187	}
1188	if (su_pclose(fd) == 255)
1189		msg_out("rpc.pcnfsd: su_pclose alert");
1190	return (stat);
1191}
1192#endif				/* SVR4 */
1193
1194/*
1195** New subsystem here. We allow the administrator to define
1196** up to NPRINTERDEFS aliases for printer names. This is done
1197** using the "/etc/pcnfsd.conf" file, which is read at startup.
1198** There are three entry points to this subsystem
1199**
1200** void add_printer_alias(char *printer, char *alias_for, char *command)
1201**
1202** This is invoked from "config_from_file()" for each
1203** "printer" line. "printer" is the name of a printer; note that
1204** it is possible to redefine an existing printer. "alias_for"
1205** is the name of the underlying printer, used for queue listing
1206** and other control functions. If it is "-", there is no
1207** underlying printer, or the administrative functions are
1208** not applicable to this printer. "command"
1209** is the command which should be run (via "su_popen()") if a
1210** job is printed on this printer. The following tokens may be
1211** embedded in the command, and are substituted as follows:
1212**
1213** $FILE	-	path to the file containing the print data
1214** $USER	-	login of user
1215** $HOST	-	hostname from which job originated
1216**
1217** Tokens may occur multiple times. If The command includes no
1218** $FILE token, the string " $FILE" is silently appended.
1219**
1220** pr_list list_virtual_printers()
1221**
1222** This is invoked from build_pr_list to generate a list of aliased
1223** printers, so that the client that asks for a list of valid printers
1224** will see these ones.
1225**
1226** char *map_printer_name(char *printer)
1227**
1228** If "printer" identifies an aliased printer, this function returns
1229** the "alias_for" name, or NULL if the "alias_for" was given as "-".
1230** Otherwise it returns its argument.
1231**
1232** char *expand_alias(char *printer, char *file, char *user, char *host)
1233**
1234** If "printer" is an aliased printer, this function returns a
1235** pointer to a static string in which the corresponding command
1236** has been expanded. Otherwise ot returns NULL.
1237*/
1238#define NPRINTERDEFS	16
1239int     num_aliases = 0;
1240struct {
1241	char   *a_printer;
1242	char   *a_alias_for;
1243	char   *a_command;
1244}       alias[NPRINTERDEFS];
1245
1246void
1247add_printer_alias(printer, alias_for, command)
1248	char   *printer;
1249	char   *alias_for;
1250	char   *command;
1251{
1252	size_t l;
1253
1254	if (num_aliases < NPRINTERDEFS) {
1255		alias[num_aliases].a_printer = strdup(printer);
1256		alias[num_aliases].a_alias_for =
1257		    (strcmp(alias_for, "-") ? strdup(alias_for) : NULL);
1258		if (strstr(command, "$FILE"))
1259			alias[num_aliases].a_command = strdup(command);
1260		else {
1261			l = strlen(command) + 8;
1262			alias[num_aliases].a_command = (char *) grab(l);
1263			strlcpy(alias[num_aliases].a_command, command, l);
1264			strlcat(alias[num_aliases].a_command, " $FILE", l);
1265		}
1266		num_aliases++;
1267	}
1268}
1269
1270pr_list
1271list_virtual_printers()
1272{
1273	pr_list first = NULL;
1274	pr_list last = NULL;
1275	pr_list curr = NULL;
1276	int     i;
1277
1278
1279	if (num_aliases == 0)
1280		return (NULL);
1281
1282	for (i = 0; i < num_aliases; i++) {
1283		curr = (struct pr_list_item *)
1284		    grab(sizeof(struct pr_list_item));
1285
1286		curr->pn = strdup(alias[i].a_printer);
1287		if (alias[i].a_alias_for == NULL)
1288			curr->device = strdup("");
1289		else
1290			curr->device = strdup(alias[i].a_alias_for);
1291		curr->remhost = strdup("");
1292		curr->cm = strdup("(alias)");
1293		curr->pr_next = NULL;
1294		if (last == NULL)
1295			first = curr;
1296		else
1297			last->pr_next = curr;
1298		last = curr;
1299
1300	}
1301	return (first);
1302}
1303
1304
1305char   *
1306map_printer_name(printer)
1307	char   *printer;
1308{
1309	int     i;
1310	for (i = 0; i < num_aliases; i++) {
1311		if (!strcmp(printer, alias[i].a_printer))
1312			return (alias[i].a_alias_for);
1313	}
1314	return (printer);
1315}
1316
1317void
1318substitute(string, token, data)
1319	char   *string;
1320	char   *token;
1321	char   *data;
1322{
1323	char    temp[512];
1324	char   *c;
1325
1326	while ((c = strstr(string, token)) != NULL) {
1327		*c = '\0';
1328		strlcpy(temp, string, sizeof(temp));
1329		strlcat(temp, data, sizeof(temp));
1330		c += strlen(token);
1331		strlcat(temp, c, sizeof(temp));
1332		strcpy(string, temp);
1333	}
1334}
1335
1336char   *
1337expand_alias(printer, file, user, host)
1338	char   *printer;
1339	char   *file;
1340	char   *user;
1341	char   *host;
1342{
1343	static char expansion[512];
1344	int     i;
1345	for (i = 0; i < num_aliases; i++) {
1346		if (!strcmp(printer, alias[i].a_printer)) {
1347			strlcpy(expansion, alias[i].a_command,
1348			    sizeof(expansion));
1349			substitute(expansion, "$FILE", file);
1350			substitute(expansion, "$USER", user);
1351			substitute(expansion, "$HOST", host);
1352			return (expansion);
1353		}
1354	}
1355	return (NULL);
1356}
1357