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