1/*
2 * CDDL HEADER START
3 *
4 * The contents of this file are subject to the terms of the
5 * Common Development and Distribution License, Version 1.0 only
6 * (the "License").  You may not use this file except in compliance
7 * with the License.
8 *
9 * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
10 * or http://www.opensolaris.org/os/licensing.
11 * See the License for the specific language governing permissions
12 * and limitations under the License.
13 *
14 * When distributing Covered Code, include this CDDL HEADER in each
15 * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
16 * If applicable, add the following below this CDDL HEADER, with the
17 * fields enclosed by brackets "[]" replaced with your own identifying
18 * information: Portions Copyright [yyyy] [name of copyright owner]
19 *
20 * CDDL HEADER END
21 */
22/*
23 * Copyright 2004 Sun Microsystems, Inc.  All rights reserved.
24 * Use is subject to license terms.
25 */
26/*	Copyright (c) 1984, 1986, 1987, 1988, 1989 AT&T	*/
27/*	  All Rights Reserved  	*/
28
29
30#pragma ident	"%Z%%M%	%I%	%E% SMI"
31
32
33#include	<unistd.h>
34#include	<stdlib.h>
35#include	<sys/types.h>
36#include	<ctype.h>
37#include	<string.h>
38#include 	<pwd.h>
39#include 	<grp.h>
40#include	<signal.h>
41#include	"ttymon.h"
42#include	"tmstruct.h"
43#include	"tmextern.h"
44
45extern	char	*strsave();
46extern	void	set_softcar();
47extern	int	vml();
48void	purge();
49static	int	get_flags();
50static	int	get_ttyflags();
51static	int	same_entry();
52static	int	check_pmtab();
53static	void	insert_pmtab();
54static	void	free_pmtab();
55static	char	*expand();
56
57int	check_identity();
58
59int	strcheck();
60
61/*
62 * read_pmtab()
63 *	- read and parse pmtab
64 *	- store table in linked list pointed by global variable "PMtab"
65 *	- exit if file does not exist or error detected.
66 */
67void
68read_pmtab()
69{
70	register struct pmtab *gptr;
71	register char *ptr, *wptr;
72	FILE 	 *fp;
73	int 	 input, state, size, rawc, field, linenum;
74	char 	 oldc;
75	char 	 line[BUFSIZ];
76	char 	 wbuf[BUFSIZ];
77	static 	 char *states[] = {
78	      "","tag","flags","identity","reserved1","reserved2","reserved3",
79	      "device","ttyflags","count","service", "timeout","ttylabel",
80	      "modules","prompt","disable msg","terminal type","soft-carrier"
81	};
82
83# ifdef DEBUG
84	debug("in read_pmtab");
85# endif
86
87	if ((fp = fopen(PMTABFILE,"r")) == NULL) {
88		fatal("open pmtab (%s) failed", PMTABFILE);
89	}
90
91	Nentries = 0;
92	if (check_version(PMTAB_VERS, PMTABFILE) != 0)
93		fatal("check pmtab version failed");
94
95	for (gptr = PMtab; gptr; gptr = gptr->p_next) {
96		if ((gptr->p_status == SESSION) ||
97		    (gptr->p_status == LOCKED) ||
98		    (gptr->p_status == UNACCESS)) {
99			if (gptr->p_fd > 0) {
100				(void)close(gptr->p_fd);
101				gptr->p_fd = 0;
102			}
103			gptr->p_inservice = gptr->p_status;
104		}
105		gptr->p_status = NOTVALID;
106	}
107
108	wptr = wbuf;
109	input = ACTIVE;
110	linenum = 0;
111	do {
112		linenum++;
113		line[0] = '\0';
114		for (ptr= line,oldc = '\0'; ptr < &line[sizeof(line)-1] &&
115		 (rawc=getc(fp))!= '\n' && rawc != EOF; ptr++,oldc=(char)rawc){
116			if ((rawc == '#') && (oldc != '\\'))
117				break;
118			*ptr = (char)rawc;
119		}
120		*ptr = '\0';
121
122		/* skip rest of the line */
123		if (rawc != EOF && rawc != '\n') {
124			if (rawc != '#')
125				log("Entry too long.\n");
126			while ((rawc = getc(fp)) != EOF && rawc != '\n')
127				;
128		}
129
130		if (rawc == EOF) {
131			if (ptr == line) break;
132			else input = FINISHED;
133		}
134
135		/* if empty line, skip */
136		for (ptr=line; *ptr != '\0' && isspace(*ptr); ptr++)
137			;
138		if (*ptr == '\0') continue;
139
140#ifdef DEBUG
141		debug("**** Next Entry ****\n%s", line);
142#endif
143		log("Processing pmtab line #%d", linenum);
144
145		/* Now we have the complete line */
146
147		if ((gptr = ALLOC_PMTAB) == PNULL)
148			fatal("memory allocation failed");
149
150		/* set hangup flag, this is the default */
151		gptr->p_ttyflags |= H_FLAG;
152
153		/*
154		 * For compatibility reasons, we cannot rely on these
155		 * having values assigned from pmtab.
156		 */
157		gptr->p_termtype = "";
158		gptr->p_softcar = "";
159
160		for (state=P_TAG,ptr=line;state !=FAILURE && state !=SUCCESS;) {
161			switch(state) {
162			case P_TAG:
163				gptr->p_tag = strsave(getword(ptr,&size,0));
164				break;
165			case P_FLAGS:
166				(void)strcpy(wptr, getword(ptr,&size,0));
167				if ((get_flags(wptr, &gptr->p_flags)) != 0) {
168					field = state;
169					state = FAILURE;
170				}
171				break;
172			case P_IDENTITY:
173				gptr->p_identity=strsave(getword(ptr,&size,0));
174				break;
175			case P_RES1:
176				gptr->p_res1=strsave(getword(ptr,&size,0));
177				break;
178			case P_RES2:
179				gptr->p_res2=strsave(getword(ptr,&size,0));
180				break;
181			case P_RES3:
182				gptr->p_res3=strsave(getword(ptr,&size,0));
183				break;
184			case P_DEVICE:
185				gptr->p_device = strsave(getword(ptr,&size,0));
186				break;
187			case P_TTYFLAGS:
188				(void)strcpy(wptr, getword(ptr,&size,0));
189				if ((get_ttyflags(wptr,&gptr->p_ttyflags))!=0) {
190					field = state;
191					state = FAILURE;
192				}
193				break;
194			case P_COUNT:
195				(void)strcpy(wptr, getword(ptr,&size,0));
196				if (strcheck(wptr, NUM) != 0) {
197					log("wait_read count must be a positive number");
198					field = state;
199					state = FAILURE;
200				}
201				else
202				    gptr->p_count = atoi(wptr);
203				break;
204			case P_SERVER:
205				gptr->p_server =
206				strsave(expand(getword(ptr,&size,1),
207					gptr->p_device));
208				break;
209			case P_TIMEOUT:
210				(void)strcpy(wptr, getword(ptr,&size,0));
211				if (strcheck(wptr, NUM) != 0) {
212					log("timeout value must be a positive number");
213					field = state;
214					state = FAILURE;
215				}
216				else
217				    gptr->p_timeout = atoi(wptr);
218				break;
219			case P_TTYLABEL:
220				gptr->p_ttylabel=strsave(getword(ptr,&size,0));
221				break;
222			case P_MODULES:
223				gptr->p_modules = strsave(getword(ptr,&size,0));
224				if (vml(gptr->p_modules) != 0) {
225					field = state;
226					state = FAILURE;
227				}
228				break;
229			case P_PROMPT:
230				gptr->p_prompt = strsave(getword(ptr,&size,TRUE));
231				break;
232			case P_DMSG:
233				gptr->p_dmsg = strsave(getword(ptr,&size,TRUE));
234				break;
235
236			case P_TERMTYPE:
237				gptr->p_termtype = strsave(getword(ptr,&size,TRUE));
238				break;
239
240			case P_SOFTCAR:
241				gptr->p_softcar = strsave(getword(ptr,&size,TRUE));
242				break;
243
244			} /* end switch */
245			ptr += size;
246			if (state == FAILURE)
247				break;
248			if (*ptr == ':') {
249				ptr++;	/* Skip the ':' */
250				state++ ;
251			} else if (*ptr != '\0') {
252				field = state;
253				state = FAILURE;
254			}
255			if (*ptr == '\0') {
256				/*
257				 * Maintain compatibility with older ttymon
258				 * pmtab files.  If Sun-added fields are
259				 * missing, this should not be an error.
260				 */
261				if (state > P_DMSG) {
262					state = SUCCESS;
263				} else {
264					field = state;
265					state = FAILURE;
266				}
267			}
268		} /* end for loop */
269
270		if (state == SUCCESS) {
271			if (check_pmtab(gptr) == 0) {
272				if (Nentries < Maxfds)
273					insert_pmtab(gptr);
274				else {
275					log("can't add more entries to "
276					    "pmtab, Maxfds = %d", Maxfds);
277					free_pmtab(gptr);
278					(void)fclose(fp);
279					return;
280				}
281			}
282			else {
283				log("Parsing failure for entry: \n%s", line);
284			log("-------------------------------------------");
285				free_pmtab(gptr);
286			}
287		} else {
288			*++ptr = '\0';
289			log("Parsing failure in the \"%s\" field,\n%s"
290			    "<--error detected here", states[field], line);
291			log("-------------------------------------------");
292			free_pmtab(gptr);
293		}
294	} while (input == ACTIVE);
295
296	(void)fclose(fp);
297	return;
298}
299
300/*
301 * get_flags	- scan flags field to set U_FLAG and X_FLAG
302 */
303static	int
304get_flags(wptr, flags)
305char	*wptr;		/* pointer to the input string	*/
306long *flags;		/* pointer to the flag to set	*/
307{
308	register char	*p;
309	for (p = wptr; *p; p++) {
310		switch (*p) {
311		case 'x':
312			*flags |= X_FLAG;
313			break;
314		case 'u':
315			*flags |= U_FLAG;
316			break;
317		default:
318			log("Invalid flag -- %c", *p);
319			return(-1);
320		}
321	}
322	return(0);
323}
324
325/*
326 * get_ttyflags	- scan ttyflags field to set corresponding flags
327 */
328static	int
329get_ttyflags(wptr, ttyflags)
330char	*wptr;		/* pointer to the input string	*/
331long 	*ttyflags;	/* pointer to the flag to be set*/
332{
333	register char	*p;
334	for (p = wptr; *p; p++) {
335		switch (*p) {
336		case 'c':
337			*ttyflags |= C_FLAG;
338			break;
339		case 'h': /* h means don't hangup */
340			*ttyflags &= ~H_FLAG;
341			break;
342		case 'b':
343			*ttyflags |= B_FLAG;
344			break;
345		case 'r':
346			*ttyflags |= R_FLAG;
347			break;
348		case 'I':
349			*ttyflags |= I_FLAG;
350			break;
351		default:
352			log("Invalid ttyflag -- %c", *p);
353			return(-1);
354		}
355	}
356	return(0);
357}
358
359# ifdef DEBUG
360/*
361 * pflags - put service flags into intelligible form for output
362 */
363
364char *
365pflags(flags)
366long flags;	/* binary representation of the flags */
367{
368	register int i;			/* scratch counter */
369	static char buf[BUFSIZ];	/* formatted flags */
370
371	if (flags == 0)
372		return("-");
373	i = 0;
374	if (flags & U_FLAG) {
375		buf[i++] = 'u';
376		flags &= ~U_FLAG;
377	}
378	if (flags & X_FLAG) {
379		buf[i++] = 'x';
380		flags &= ~X_FLAG;
381	}
382	if (flags)
383		log("Internal error in pflags");
384	buf[i] = '\0';
385	return(buf);
386}
387
388/*
389 * pttyflags - put ttyflags into intelligible form for output
390 */
391
392char *
393pttyflags(flags)
394long flags;	/* binary representation of ttyflags */
395{
396	register int i;			/* scratch counter */
397	static char buf[BUFSIZ];	/* formatted flags */
398
399	if (flags == 0)
400		return("h");
401	i = 0;
402	if (flags & C_FLAG) {
403		buf[i++] = 'c';
404		flags &= ~C_FLAG;
405	}
406	if (flags & H_FLAG)
407		flags &= ~H_FLAG;
408	else
409		buf[i++] = 'h';
410	if (flags & B_FLAG) {
411		buf[i++] = 'b';
412		flags &= ~B_FLAG;
413	}
414	if (flags & R_FLAG) {
415		buf[i++] = 'r';
416		flags &= ~B_FLAG;
417	}
418	if (flags & I_FLAG) {
419		buf[i++] = 'I';
420		flags &= ~I_FLAG;
421	}
422	if (flags)
423		log("Internal error in p_ttyflags");
424	buf[i] = '\0';
425	return(buf);
426}
427
428void
429dump_pmtab()
430{
431	struct	pmtab *gptr;
432
433	debug("in dump_pmtab");
434	log("********** dumping pmtab **********");
435	log(" ");
436	for (gptr=PMtab; gptr; gptr = gptr->p_next) {
437		log("-------------------------------------------");
438		log("tag:\t\t%s", gptr->p_tag);
439		log("flags:\t\t%s",pflags(gptr->p_flags));
440		log("identity:\t%s", gptr->p_identity);
441		log("reserved1:\t%s", gptr->p_res1);
442		log("reserved2:\t%s", gptr->p_res2);
443		log("reserved3:\t%s", gptr->p_res3);
444		log("device:\t%s", gptr->p_device);
445		log("ttyflags:\t%s",pttyflags(gptr->p_ttyflags));
446		log("count:\t\t%d", gptr->p_count);
447		log("server:\t%s", gptr->p_server);
448		log("timeout:\t%d", gptr->p_timeout);
449		log("ttylabel:\t%s", gptr->p_ttylabel);
450		log("modules:\t%s", gptr->p_modules);
451		log("prompt:\t%s", gptr->p_prompt);
452		log("disable msg:\t%s", gptr->p_dmsg);
453		log("terminal type:\t%s", gptr->p_termtype);
454		log("soft-carrier:\t%s", gptr->p_softcar);
455		log("status:\t\t%d", gptr->p_status);
456		log("inservice:\t%d", gptr->p_inservice);
457		log("fd:\t\t%d", gptr->p_fd);
458		log("pid:\t\t%ld", gptr->p_pid);
459		log("uid:\t\t%ld", gptr->p_uid);
460		log("gid:\t\t%ld", gptr->p_gid);
461		log("dir:\t%s", gptr->p_dir);
462		log(" ");
463	}
464	log("********** end dumping pmtab **********");
465}
466# endif
467
468/*
469 * same_entry(e1,e2) -    compare 2 entries of pmtab
470 *			if the fields are different, copy e2 to e1
471 * 			return 1 if same, return 0 if different
472 */
473static	int
474same_entry(e1,e2)
475struct	pmtab	*e1,*e2;
476{
477
478	if (strcmp(e1->p_identity, e2->p_identity) != 0)
479		return(0);
480	if (strcmp(e1->p_res1, e2->p_res1) != 0)
481		return(0);
482	if (strcmp(e1->p_res2, e2->p_res2) != 0)
483		return(0);
484	if (strcmp(e1->p_res3, e2->p_res3) != 0)
485		return(0);
486	if (strcmp(e1->p_device, e2->p_device) != 0)
487		return(0);
488	if (strcmp(e1->p_server, e2->p_server) != 0)
489		return(0);
490	if (strcmp(e1->p_ttylabel, e2->p_ttylabel) != 0)
491		return(0);
492	if (strcmp(e1->p_modules, e2->p_modules) != 0)
493		return(0);
494	if (strcmp(e1->p_prompt, e2->p_prompt) != 0)
495		return(0);
496	if (strcmp(e1->p_dmsg, e2->p_dmsg) != 0)
497		return(0);
498	if (strcmp(e1->p_termtype, e2->p_termtype) != 0)
499		return(0);
500	if (strcmp(e1->p_softcar, e2->p_softcar) != 0)
501		return(0);
502	if (e1->p_flags != e2->p_flags)
503		return(0);
504	/*
505	 * compare lowest 4 bits only,
506	 * because A_FLAG is not part of original ttyflags
507	 */
508	if ((e1->p_ttyflags & ~A_FLAG) != (e2->p_ttyflags & ~A_FLAG))
509		return(0);
510	if (e1->p_count != e2->p_count)
511		return(0);
512	if (e1->p_timeout != e2->p_timeout)
513		return(0);
514	if (e1->p_uid != e2->p_uid)
515		return(0);
516	if (e1->p_gid != e2->p_gid)
517		return(0);
518	if (strcmp(e1->p_dir, e2->p_dir) != 0)
519		return(0);
520	return(1);
521}
522
523
524/*
525 * insert_pmtab - insert a pmtab entry into the linked list
526 */
527
528static	void
529insert_pmtab(sp)
530register struct pmtab *sp;	/* ptr to entry to be inserted */
531{
532	register struct pmtab *tsp, *savtsp;	/* scratch pointers */
533	int ret;				/* strcmp return value */
534
535# ifdef DEBUG
536	debug("in insert_pmtab");
537# endif
538	savtsp = tsp = PMtab;
539
540/*
541 * find the correct place to insert this element
542 */
543
544	while (tsp) {
545		ret = strcmp(sp->p_tag, tsp->p_tag);
546		if (ret > 0) {
547			/* keep on looking */
548			savtsp = tsp;
549			tsp = tsp->p_next;
550			continue;
551		}
552		else if (ret == 0) {
553			if (tsp->p_status) {
554				/* this is a duplicate entry, ignore it */
555				log("Ignoring duplicate entry for <%s>",
556				    tsp->p_tag);
557			}
558			else {
559				if (same_entry(tsp,sp)) {  /* same entry */
560					tsp->p_status = VALID;
561				}
562				else {	/* entry changed */
563					if ((sp->p_flags & X_FLAG) &&
564						((sp->p_dmsg == NULL) ||
565						(*(sp->p_dmsg) == '\0'))) {
566						/* disabled entry */
567						tsp->p_status = NOTVALID;
568					}
569					else {
570# ifdef DEBUG
571					debug("replacing <%s>", sp->p_tag);
572# endif
573						/* replace old entry */
574						sp->p_next = tsp->p_next;
575						if (tsp == PMtab) {
576						   PMtab = sp;
577						}
578						else {
579						   savtsp->p_next = sp;
580						}
581						sp->p_status = CHANGED;
582						sp->p_fd = tsp->p_fd;
583						sp->p_pid = tsp->p_pid;
584					        sp->p_inservice =
585							tsp->p_inservice;
586						sp = tsp;
587					}
588				}
589				Nentries++;
590			}
591			free_pmtab(sp);
592			return;
593		}
594		else {
595			if ((sp->p_flags & X_FLAG) &&
596				((sp->p_dmsg == NULL) ||
597				(*(sp->p_dmsg) == '\0'))) { /* disabled entry */
598				free_pmtab(sp);
599				return;
600			}
601			/*
602			 * Set the state of soft-carrier.
603			 * Since this is a one-time only operation,
604			 * we do it when this service is added to
605			 * the enabled list.
606			 */
607			if (*sp->p_softcar != '\0')
608				set_softcar(sp);
609
610			/* insert it here */
611			if (tsp == PMtab) {
612				sp->p_next = PMtab;
613				PMtab = sp;
614			}
615			else {
616				sp->p_next = savtsp->p_next;
617				savtsp->p_next = sp;
618			}
619# ifdef DEBUG
620			debug("adding <%s>", sp->p_tag);
621# endif
622			Nentries++;
623			/* this entry is "current" */
624			sp->p_status = VALID;
625			return;
626		}
627	}
628
629/*
630 * either an empty list or should put element at end of list
631 */
632
633	if ((sp->p_flags & X_FLAG) &&
634		((sp->p_dmsg == NULL) ||
635		(*(sp->p_dmsg) == '\0'))) { /* disabled entry */
636		free_pmtab(sp);		 /* do not poll this entry */
637		return;
638	}
639	/*
640	 * Set the state of soft-carrier.
641	 * Since this is a one-time only operation,
642	 * we do it when this service is added to
643	 * the enabled list.
644	 */
645	if (*sp->p_softcar != '\0')
646		set_softcar(sp);
647	sp->p_next = NULL;
648	if (PMtab == NULL)
649		PMtab = sp;
650	else
651		savtsp->p_next = sp;
652# ifdef DEBUG
653	debug("adding <%s>", sp->p_tag);
654# endif
655	++Nentries;
656	/* this entry is "current" */
657	sp->p_status = VALID;
658}
659
660
661/*
662 * purge - purge linked list of "old" entries
663 */
664
665
666void
667purge()
668{
669	register struct pmtab *sp;		/* working pointer */
670	register struct pmtab *savesp, *tsp;	/* scratch pointers */
671
672# ifdef DEBUG
673	debug("in purge");
674# endif
675	sp = savesp = PMtab;
676	while (sp) {
677		if (sp->p_status) {
678# ifdef DEBUG
679			debug("p_status not 0");
680# endif
681			savesp = sp;
682			sp = sp->p_next;
683		}
684		else {
685			tsp = sp;
686			if (tsp == PMtab) {
687				PMtab = sp->p_next;
688				savesp = PMtab;
689			}
690			else
691				savesp->p_next = sp->p_next;
692# ifdef DEBUG
693			debug("purging <%s>", sp->p_tag);
694# endif
695			sp = sp->p_next;
696			free_pmtab(tsp);
697		}
698	}
699}
700
701/*
702 *	free_pmtab	- free one pmtab entry
703 */
704static	void
705free_pmtab(p)
706struct	pmtab	*p;
707{
708#ifdef	DEBUG
709	debug("in free_pmtab");
710#endif
711	free(p->p_tag);
712	free(p->p_identity);
713	free(p->p_res1);
714	free(p->p_res2);
715	free(p->p_res3);
716	free(p->p_device);
717	free(p->p_server);
718	free(p->p_ttylabel);
719	free(p->p_modules);
720	free(p->p_prompt);
721	free(p->p_dmsg);
722	free(p->p_termtype);
723	free(p->p_softcar);
724	if (p->p_dir)
725		free(p->p_dir);
726	free(p);
727}
728
729/*
730 *	check_pmtab - check the fields to make sure things are correct
731 *		    - return 0 if everything is ok
732 *		    - return -1 if something is wrong
733 */
734
735static	int
736check_pmtab(p)
737struct	pmtab	*p;
738{
739	if (p == NULL) {
740		log("pmtab ptr is NULL");
741		return(-1);
742	}
743
744	/* check service tag */
745	if ((p->p_tag == NULL) || (*(p->p_tag) == '\0')) {
746		log("port/service tag is missing");
747		return(-1);
748	}
749	if (strlen(p->p_tag) > (size_t)(MAXID - 1)) {
750		log("port/service tag <%s> is longer than %d", p->p_tag,
751		    MAXID-1);
752		return(-1);
753	}
754	if (strcheck(p->p_tag, ALNUM) != 0) {
755		log("port/service tag <%s> is not alphanumeric", p->p_tag);
756		return(-1);
757	}
758	if (check_identity(p) != 0) {
759		return(-1);
760	}
761
762	if (check_device(p->p_device) != 0)
763		return(-1);
764
765	if (check_cmd(p->p_server) != 0)
766		return(-1);
767	return(0);
768}
769
770extern  struct 	passwd *getpwnam();
771extern  void 	endpwent();
772extern  struct 	group *getgrgid();
773extern  void 	endgrent();
774
775/*
776 *	check_identity - check to see if the identity is a valid user
777 *		       - log name in the passwd file,
778 *		       - and if its group id is a valid one
779 *		  	- return 0 if everything is ok. Otherwise, return -1
780 */
781
782int
783check_identity(p)
784struct	pmtab	*p;
785{
786	register struct passwd *pwdp;
787
788	if ((p->p_identity == NULL) || (*(p->p_identity) == '\0')) {
789		log("identity field is missing");
790		return(-1);
791	}
792	if ((pwdp = getpwnam(p->p_identity)) == NULL) {
793		log("missing or bad passwd entry for <%s>", p->p_identity);
794		endpwent();
795		return(-1);
796	}
797	if (getgrgid(pwdp->pw_gid) == NULL) {
798		log("no group entry for %ld", pwdp->pw_gid);
799		endgrent();
800		endpwent();
801		return(-1);
802	}
803	p->p_uid = pwdp->pw_uid;
804	p->p_gid = pwdp->pw_gid;
805	p->p_dir = strsave(pwdp->pw_dir);
806	endgrent();
807	endpwent();
808	return(0);
809}
810
811/*
812 * expand(cmdp, devp)	- expand %d to device name and %% to %,
813 *				- any other characters are untouched.
814 *				- return the expanded string
815 */
816static char	*
817expand(cmdp,devp)
818char	*cmdp;		/* ptr to cmd string	*/
819char	*devp;		/* ptr to device name	*/
820{
821	register char	*cp, *dp, *np;
822	static char	buf[BUFSIZ];
823	cp = cmdp;
824	np = buf;
825	dp = devp;
826	while (*cp) {
827		if (*cp != '%') {
828			*np++ = *cp++;
829			continue;
830		}
831		switch (*++cp) {
832		case 'd':
833			while (*dp) {
834				*np++ = *dp++;
835			}
836			cp++;
837			break;
838		case '%':
839			*np++ = *cp++;
840			break;
841		default:
842			*np++ = *cp++;
843			break;
844		}
845	}
846	*np = '\0';
847	return(buf);
848}
849
850