1/*
2 * $Id: printcap.c,v 1.12 2009-10-14 02:24:05 didg Exp $
3 *
4 * Copyright (c) 1990,1994 Regents of The University of Michigan.
5 * All Rights Reserved.  See COPYRIGHT.
6 *
7 * Copyright (c) 1983 Regents of the University of California.
8 * All rights reserved.
9 *
10 * Redistribution and use in source and binary forms, with or without
11 * modification, are permitted provided that the following conditions
12 * are met:
13 * 1. Redistributions of source code must retain the above copyright
14 *    notice, this list of conditions and the following disclaimer.
15 * 2. Redistributions in binary form must reproduce the above copyright
16 *    notice, this list of conditions and the following disclaimer in the
17 *    documentation and/or other materials provided with the distribution.
18 * 3. All advertising materials mentioning features or use of this software
19 *    must display the following acknowledgement:
20 *	This product includes software developed by the University of
21 *	California, Berkeley and its contributors.
22 * 4. Neither the name of the University nor the names of its contributors
23 *    may be used to endorse or promote products derived from this software
24 *    without specific prior written permission.
25 *
26 * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
27 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
28 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
29 * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
30 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
31 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
32 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
33 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
34 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
35 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
36 * SUCH DAMAGE.
37 */
38
39#ifdef HAVE_CONFIG_H
40#include "config.h"
41#endif /* HAVE_CONFIG_H */
42
43#include <ctype.h>
44#include <stdio.h>
45#include <string.h>
46#include <stdlib.h>
47
48#ifdef HAVE_UNISTD_H
49#include <unistd.h>
50#endif /* HAVE_UNISTD_H */
51#include <sys/types.h>
52#include <sys/stat.h>
53#ifdef HAVE_FCNTL_H
54#include <fcntl.h>
55#endif /* HAVE_FCNTL_H */
56#include <atalk/paths.h>
57
58#include "printcap.h"
59
60#ifndef BUFSIZ
61#define	BUFSIZ	1024
62#endif /* ! BUFSIZ */
63#define MAXHOP	32	/* max number of tc= indirections */
64
65/*
66 * termcap - routines for dealing with the terminal capability data base
67 *
68 * BUG:		Should use a "last" pointer in tbuf, so that searching
69 *		for capabilities alphabetically would not be a n**2/2
70 *		process when large numbers of capabilities are given.
71 * Note:	If we add a last pointer now we will screw up the
72 *		tc capability. We really should compile termcap.
73 *
74 * Essentially all the work here is scanning and decoding escapes
75 * in string capabilities.  We don't use stdio because the editor
76 * doesn't, and because living w/o it is not hard.
77 */
78
79#define PRINTCAP
80
81#ifdef PRINTCAP
82#define tgetent	pgetent
83#define tskip	pskip
84#define tgetstr	pgetstr
85#define tdecode pdecode
86#define tgetnum	pgetnum
87#define	tgetflag pgetflag
88#define tdecode pdecode
89#define tnchktc	pnchktc
90#define	tnamatch pnamatch
91#define V6
92#endif /* PRINTCAP */
93
94static	FILE *pfp = NULL;	/* printcap data base file pointer */
95static	char *tbuf;
96static	int hopcount;		/* detect infinite loops in termcap, init 0 */
97
98/*
99 * Similar to tgetent except it returns the next entry instead of
100 * doing a lookup.
101 *
102 * Added a "cap" parameter, so we can use these calls for printcap
103 * and papd.conf.
104 */
105int getprent( char *cap, char *bp, int bufsize)
106{
107	register int c, skip = 0, i;
108
109	if (pfp == NULL && (pfp = fopen( cap, "r")) == NULL)
110		return(-1);
111	tbuf = bp;
112	i = 0;
113	for (;;) {
114		switch (c = getc(pfp)) {
115		case EOF:
116                        if (bp != tbuf) {
117				*bp = '\0';
118				return(1);
119			}
120			fclose(pfp);
121			pfp = NULL;
122			return(0);
123		case '\n':
124			if (bp == tbuf) {
125				skip = 0;
126				continue;
127			}
128			if (bp[-1] == '\\') {
129				bp--;
130				continue;
131			}
132			*bp = '\0';
133			return(1);
134		case '#':
135			if (bp == tbuf)
136				skip++;
137		default:
138			if (skip)
139				continue;
140			if (bp >= tbuf+BUFSIZ) {
141				write(2, "Termcap entry too long\n", 23);
142				*bp = '\0';
143				return(1);
144			}
145			*bp++ = c;
146			if (++i >= bufsize) {
147				write(2, "config file too large\n", 22);
148				fclose(pfp);
149				pfp = NULL;
150				*bp = '\0';
151				return(1);
152			}
153		}
154	}
155}
156
157void endprent(void)
158{
159	if (pfp != NULL)
160		fclose(pfp);
161}
162
163/*
164 * Get an entry for terminal name in buffer bp,
165 * from the termcap file.  Parse is very rudimentary;
166 * we just notice escaped newlines.
167 *
168 * Added a "cap" parameter, so we can use these calls for printcap
169 * and papd.conf.
170 */
171int tgetent(char *cap, char *bp, char *name)
172{
173	register char *cp;
174	register int c;
175	register int i = 0, cnt = 0;
176	char ibuf[BUFSIZ];
177	int tf;
178	int skip;
179
180	hopcount = 0;
181	tbuf = bp;
182	tf = 0;
183#ifndef V6
184	cp = getenv("TERMCAP");
185	/*
186	 * TERMCAP can have one of two things in it. It can be the
187	 * name of a file to use instead of /etc/termcap. In this
188	 * case it better start with a "/". Or it can be an entry to
189	 * use so we don't have to read the file. In this case it
190	 * has to already have the newlines crunched out.
191	 */
192	if (cp && *cp) {
193		if (*cp!='/') {
194			cp2 = getenv("TERM");
195			if (cp2==(char *) 0 || strcmp(name,cp2)==0) {
196				strcpy(bp,cp);
197				return(tnchktc(cap));
198			} else {
199				tf = open(cap, 0);
200			}
201		} else
202			tf = open(cp, 0);
203	}
204	if (tf==0)
205		tf = open(cap, 0);
206#else /* V6 */
207	tf = open(cap, 0);
208#endif /* V6 */
209	if (tf < 0)
210		return (-1);
211	for (;;) {
212		cp = bp;
213		skip = 0;
214		for (;;) {
215			if (i == cnt) {
216				cnt = read(tf, ibuf, BUFSIZ);
217				if (cnt <= 0) {
218					close(tf);
219					return (0);
220				}
221				i = 0;
222			}
223			c = ibuf[i++];
224			if (c == '\n') {
225				if (!skip && cp > bp && cp[-1] == '\\') {
226					cp--;
227					continue;
228				}
229				skip = 0;
230				if (cp == bp)
231					continue;
232				else
233					break;
234			}
235			if (c == '#' && cp == bp)
236				skip++;
237			if (skip)
238				continue;
239			if (cp >= bp+BUFSIZ) {
240				write(2,"Termcap entry too long\n", 23);
241				break;
242			} else
243				*cp++ = c;
244		}
245		*cp = 0;
246
247		/*
248		 * The real work for the match.
249		 */
250		if (tnamatch(name)) {
251			close(tf);
252			return(tnchktc(cap));
253		}
254	}
255}
256
257/*
258 * tnchktc: check the last entry, see if it's tc=xxx. If so,
259 * recursively find xxx and append that entry (minus the names)
260 * to take the place of the tc=xxx entry. This allows termcap
261 * entries to say "like an HP2621 but doesn't turn on the labels".
262 * Note that this works because of the left to right scan.
263 *
264 * Added a "cap" parameter, so we can use these calls for printcap
265 * and papd.conf.
266 */
267int tnchktc( char *cap)
268{
269	register char *p, *q;
270	char tcname[16];	/* name of similar terminal */
271	char tcbuf[BUFSIZ];
272	char *holdtbuf = tbuf;
273	int l;
274
275	p = tbuf + strlen(tbuf) - 2;	/* before the last colon */
276	while (*--p != ':')
277		if (p<tbuf) {
278			write(2, "Bad termcap entry\n", 18);
279			return (0);
280		}
281	p++;
282	/* p now points to beginning of last field */
283	if (p[0] != 't' || p[1] != 'c')
284		return(1);
285	strcpy(tcname,p+3);
286	q = tcname;
287	while (q && *q != ':')
288		q++;
289	*q = 0;
290	if (++hopcount > MAXHOP) {
291		write(2, "Infinite tc= loop\n", 18);
292		return (0);
293	}
294	if (tgetent( cap, tcbuf, tcname) != 1)
295		return(0);
296	for (q=tcbuf; *q != ':'; q++)
297		;
298	l = p - holdtbuf + strlen(q);
299	if (l > BUFSIZ) {
300		write(2, "Termcap entry too long\n", 23);
301		q[BUFSIZ - (p-tbuf)] = 0;
302	}
303	strcpy(p, q+1);
304	tbuf = holdtbuf;
305	return(1);
306}
307
308/*
309 * Tnamatch deals with name matching.  The first field of the termcap
310 * entry is a sequence of names separated by |'s, so we compare
311 * against each such name.  The normal : terminator after the last
312 * name (before the first field) stops us.
313 */
314int tnamatch(char *np)
315{
316	register char *Np, *Bp;
317
318	Bp = tbuf;
319	if (*Bp == '#')
320		return(0);
321	for (;;) {
322		for (Np = np; *Np && *Bp == *Np; Bp++, Np++)
323			continue;
324		if (*Np == 0 && (*Bp == '|' || *Bp == ':' || *Bp == 0))
325			return (1);
326		while (*Bp && *Bp != ':' && *Bp != '|')
327			Bp++;
328		if (*Bp == 0 || *Bp == ':')
329			return (0);
330		Bp++;
331	}
332}
333
334/*
335 * Skip to the next field.  Notice that this is very dumb, not
336 * knowing about \: escapes or any such.  If necessary, :'s can be put
337 * into the termcap file in octal.
338 */
339static char *tskip(char *bp)
340{
341
342	while (*bp && *bp != ':')
343		bp++;
344	while (*bp && *bp == ':')
345		bp++;
346	return (bp);
347}
348
349/*
350 * Return the (numeric) option id.
351 * Numeric options look like
352 *	li#80
353 * i.e. the option string is separated from the numeric value by
354 * a # character.  If the option is not found we return -1.
355 * Note that we handle octal numbers beginning with 0.
356 */
357int tgetnum(char *id)
358{
359	register int i, base;
360	register char *bp = tbuf;
361
362	for (;;) {
363		bp = tskip(bp);
364		if (*bp == 0)
365			return (-1);
366		if (*bp++ != id[0] || *bp == 0 || *bp++ != id[1])
367			continue;
368		if (*bp == '@')
369			return(-1);
370		if (*bp != '#')
371			continue;
372		bp++;
373		base = 10;
374		if (*bp == '0')
375			base = 8;
376		i = 0;
377		while (isdigit(*bp))
378			i *= base, i += *bp++ - '0';
379		return (i);
380	}
381}
382
383/*
384 * Handle a flag option.
385 * Flag options are given "naked", i.e. followed by a : or the end
386 * of the buffer.  Return 1 if we find the option, or 0 if it is
387 * not given.
388 */
389int tgetflag(char *id)
390{
391	register char *bp = tbuf;
392
393	for (;;) {
394		bp = tskip(bp);
395		if (!*bp)
396			return (0);
397		if (*bp++ == id[0] && *bp != 0 && *bp++ == id[1]) {
398			if (!*bp || *bp == ':')
399				return (1);
400			else if (*bp == '@')
401				return(0);
402		}
403	}
404}
405
406/*
407 * Tdecode does the grung work to decode the
408 * string capability escapes.
409 */
410static char *
411tdecode(char *str, char **area)
412{
413	register char *cp;
414	register int c;
415	register char *dp;
416	int i;
417
418	cp = *area;
419	while ((c = *str++) && c != ':') {
420		switch (c) {
421
422		case '^':
423			c = *str++ & 037;
424			break;
425
426		case '\\':
427			dp = "E\033^^\\\\::n\nr\rt\tb\bf\f";
428			c = *str++;
429nextc:
430			if (*dp++ == c) {
431				c = *dp++;
432				break;
433			}
434			dp++;
435			if (*dp)
436				goto nextc;
437			if (isdigit(c)) {
438				c -= '0', i = 2;
439				do
440					c <<= 3, c |= *str++ - '0';
441				while (--i && isdigit(*str));
442			}
443			break;
444		}
445		*cp++ = c;
446	}
447	*cp++ = 0;
448	str = *area;
449	*area = cp;
450	return (str);
451}
452
453/*
454 * Get a string valued option.
455 * These are given as
456 *	cl=^Z
457 * Much decoding is done on the strings, and the strings are
458 * placed in area, which is a ref parameter which is updated.
459 * No checking on area overflow.
460 */
461char *
462tgetstr(char *id, char **area)
463{
464	register char *bp = tbuf;
465
466	for (;;) {
467		bp = tskip(bp);
468		if (!*bp)
469			return (NULL);
470		if (*bp++ != id[0] || *bp == 0 || *bp++ != id[1])
471			continue;
472		if (*bp == '@')
473			return(NULL);
474		if (*bp != '=')
475			continue;
476		bp++;
477		return (tdecode(bp, area));
478	}
479}
480
481static char *
482decodename(char *str, char **area, int bufsize)
483{
484	register char *cp;
485	register int c;
486	register char *dp;
487	int i;
488
489	cp = *area;
490	while ((c = *str++) && --bufsize && c != ':' && c != '|' ) {
491		switch (c) {
492
493		case '^':
494			c = *str++ & 037;
495			break;
496
497		case '\\':
498			dp = "E\033^^\\\\::n\nr\rt\tb\bf\f";
499			c = *str++;
500nextc:
501			if (*dp++ == c) {
502				c = *dp++;
503				break;
504			}
505			dp++;
506			if (*dp)
507				goto nextc;
508			if (isdigit(c)) {
509				c -= '0', i = 2;
510				do
511					c <<= 3, c |= *str++ - '0';
512				while (--i && isdigit(*str));
513			}
514			break;
515		}
516		*cp++ = c;
517	}
518	*cp++ = 0;
519	str = *area;
520	*area = cp;
521	return (str);
522}
523
524char *
525getpname(char **area, int bufsize)
526{
527	return( decodename( tbuf, area, bufsize));
528}
529