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