171333Sitojun/*	$FreeBSD$	*/
2118786Sume/*	$KAME: advcap.c,v 1.11 2003/05/19 09:46:50 keiichi Exp $	*/
362656Skris
455505Sshin/*
555505Sshin * Copyright (c) 1983 The Regents of the University of California.
655505Sshin * All rights reserved.
755505Sshin *
855505Sshin * Redistribution and use in source and binary forms, with or without
955505Sshin * modification, are permitted provided that the following conditions
1055505Sshin * are met:
1155505Sshin * 1. Redistributions of source code must retain the above copyright
1255505Sshin *    notice, this list of conditions and the following disclaimer.
1355505Sshin * 2. Redistributions in binary form must reproduce the above copyright
1455505Sshin *    notice, this list of conditions and the following disclaimer in the
1555505Sshin *    documentation and/or other materials provided with the distribution.
1655505Sshin * 4. Neither the name of the University nor the names of its contributors
1755505Sshin *    may be used to endorse or promote products derived from this software
1855505Sshin *    without specific prior written permission.
1955505Sshin *
2055505Sshin * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
2155505Sshin * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
2255505Sshin * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
2355505Sshin * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
2455505Sshin * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
2555505Sshin * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
2655505Sshin * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
2755505Sshin * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
2855505Sshin * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
2955505Sshin * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
3055505Sshin * SUCH DAMAGE.
3155505Sshin */
3255505Sshin
3355505Sshin/*
3455505Sshin * remcap - routines for dealing with the remote host data base
3555505Sshin *
3655505Sshin * derived from termcap
3755505Sshin */
3855505Sshin#include <sys/types.h>
3955505Sshin#include <sys/uio.h>
4055505Sshin#include <unistd.h>
4155505Sshin#include <fcntl.h>
4255505Sshin#include <ctype.h>
4355505Sshin#include <stdlib.h>
4455505Sshin#include <stdio.h>
4555505Sshin#include <syslog.h>
4655505Sshin#include <errno.h>
4755505Sshin#include <string.h>
4855505Sshin#include "pathnames.h"
4955505Sshin
5055505Sshin#ifndef BUFSIZ
5155505Sshin#define	BUFSIZ		1024
5255505Sshin#endif
5355505Sshin#define MAXHOP		32		/* max number of tc= indirections */
5455505Sshin
5555505Sshin#define	tgetent		agetent
5655505Sshin#define	tnchktc		anchktc
5755505Sshin#define	tnamatch	anamatch
5855505Sshin#define	tgetnum		agetnum
5955505Sshin#define	tgetflag	agetflag
6055505Sshin#define	tgetstr		agetstr
6155505Sshin
6262656Skris#if 0
6362656Skris#define V_TERMCAP	"REMOTE"
6462656Skris#define V_TERM		"HOST"
6562656Skris#endif
6662656Skris
6755505Sshin/*
6855505Sshin * termcap - routines for dealing with the terminal capability data base
6955505Sshin *
7055505Sshin * BUG:		Should use a "last" pointer in tbuf, so that searching
7155505Sshin *		for capabilities alphabetically would not be a n**2/2
7255505Sshin *		process when large numbers of capabilities are given.
7355505Sshin * Note:	If we add a last pointer now we will screw up the
7455505Sshin *		tc capability. We really should compile termcap.
7555505Sshin *
7655505Sshin * Essentially all the work here is scanning and decoding escapes
7755505Sshin * in string capabilities.  We don't use stdio because the editor
7855505Sshin * doesn't, and because living w/o it is not hard.
7955505Sshin */
8055505Sshin
8162656Skrisstatic	char *tbuf;
8262656Skrisstatic	int hopcount;	/* detect infinite loops in termcap, init 0 */
8355505Sshin
84222732Shrsextern const char *conffile;
8555505Sshin
86173412Skevloint tgetent(char *, char *);
87222732Shrsint getent(char *, char *, const char *);
88173412Skevloint tnchktc(void);
89173412Skevloint tnamatch(char *);
90173412Skevlostatic char *tskip(char *);
91173412Skevloint64_t tgetnum(char *);
92173412Skevloint tgetflag(char *);
93173412Skevlochar *tgetstr(char *, char **);
94173412Skevlostatic char *tdecode(char *, char **);
9555505Sshin
9655505Sshin/*
9755505Sshin * Get an entry for terminal name in buffer bp,
9855505Sshin * from the termcap file.  Parse is very rudimentary;
9955505Sshin * we just notice escaped newlines.
10055505Sshin */
10155505Sshinint
102222732Shrstgetent(char *bp, char *name)
10355505Sshin{
104222732Shrs	return (getent(bp, name, conffile));
10555505Sshin}
10655505Sshin
10755505Sshinint
108222732Shrsgetent(char *bp, char *name, const char *cfile)
10955505Sshin{
110118662Sume	int c;
111118662Sume	int i = 0, cnt = 0;
11255505Sshin	char ibuf[BUFSIZ];
113222732Shrs	char *cp;
11455505Sshin	int tf;
11555505Sshin
11655505Sshin	tbuf = bp;
11755505Sshin	tf = 0;
11855505Sshin	/*
11955505Sshin	 * TERMCAP can have one of two things in it. It can be the
12055505Sshin	 * name of a file to use instead of /etc/termcap. In this
12155505Sshin	 * case it better start with a "/". Or it can be an entry to
12255505Sshin	 * use so we don't have to read the file. In this case it
12355505Sshin	 * has to already have the newlines crunched out.
12455505Sshin	 */
125222732Shrs	if (cfile && *cfile)
126222732Shrs		tf = open(cfile, O_RDONLY);
127222732Shrs
12855505Sshin	if (tf < 0) {
12962656Skris		syslog(LOG_INFO,
130118660Sume		       "<%s> open: %s", __func__, strerror(errno));
13155505Sshin		return (-2);
13255505Sshin	}
13355505Sshin	for (;;) {
13455505Sshin		cp = bp;
13555505Sshin		for (;;) {
13655505Sshin			if (i == cnt) {
13755505Sshin				cnt = read(tf, ibuf, BUFSIZ);
13855505Sshin				if (cnt <= 0) {
13955505Sshin					close(tf);
14055505Sshin					return (0);
14155505Sshin				}
14255505Sshin				i = 0;
14355505Sshin			}
14455505Sshin			c = ibuf[i++];
14555505Sshin			if (c == '\n') {
14655505Sshin				if (cp > bp && cp[-1] == '\\') {
14755505Sshin					cp--;
14855505Sshin					continue;
14955505Sshin				}
15055505Sshin				break;
15155505Sshin			}
152118664Sume			if (cp >= bp + BUFSIZ) {
15380381Ssheldonh				write(STDERR_FILENO, "Remcap entry too long\n",
15480381Ssheldonh				      23);
15555505Sshin				break;
15655505Sshin			} else
15755505Sshin				*cp++ = c;
15855505Sshin		}
15955505Sshin		*cp = 0;
16055505Sshin
16155505Sshin		/*
16255505Sshin		 * The real work for the match.
16355505Sshin		 */
16455505Sshin		if (tnamatch(name)) {
16555505Sshin			close(tf);
16655505Sshin			return (tnchktc());
16755505Sshin		}
16855505Sshin	}
16955505Sshin}
17055505Sshin
17155505Sshin/*
17255505Sshin * tnchktc: check the last entry, see if it's tc=xxx. If so,
17355505Sshin * recursively find xxx and append that entry (minus the names)
17455505Sshin * to take the place of the tc=xxx entry. This allows termcap
17555505Sshin * entries to say "like an HP2621 but doesn't turn on the labels".
17655505Sshin * Note that this works because of the left to right scan.
17755505Sshin */
17855505Sshinint
179222732Shrstnchktc(void)
18055505Sshin{
181118662Sume	char *p, *q;
18255505Sshin	char tcname[16];	/* name of similar terminal */
18355505Sshin	char tcbuf[BUFSIZ];
18455505Sshin	char *holdtbuf = tbuf;
18555505Sshin	int l;
18655505Sshin
18755505Sshin	p = tbuf + strlen(tbuf) - 2;	/* before the last colon */
18855505Sshin	while (*--p != ':')
189118664Sume		if (p < tbuf) {
19080381Ssheldonh			write(STDERR_FILENO, "Bad remcap entry\n", 18);
19155505Sshin			return (0);
19255505Sshin		}
19355505Sshin	p++;
19455505Sshin	/* p now points to beginning of last field */
19555505Sshin	if (p[0] != 't' || p[1] != 'c')
19655505Sshin		return (1);
197118786Sume	strlcpy(tcname, p + 3, sizeof tcname);
19855505Sshin	q = tcname;
19955505Sshin	while (*q && *q != ':')
20055505Sshin		q++;
20155505Sshin	*q = 0;
20255505Sshin	if (++hopcount > MAXHOP) {
20380381Ssheldonh		write(STDERR_FILENO, "Infinite tc= loop\n", 18);
20455505Sshin		return (0);
20555505Sshin	}
206222824Shrs	if (getent(tcbuf, tcname, conffile) != 1) {
20755505Sshin		return (0);
20855505Sshin	}
20955505Sshin	for (q = tcbuf; *q++ != ':'; )
21055505Sshin		;
21155505Sshin	l = p - holdtbuf + strlen(q);
21255505Sshin	if (l > BUFSIZ) {
21380381Ssheldonh		write(STDERR_FILENO, "Remcap entry too long\n", 23);
21455505Sshin		q[BUFSIZ - (p-holdtbuf)] = 0;
21555505Sshin	}
21655505Sshin	strcpy(p, q);
21755505Sshin	tbuf = holdtbuf;
21855505Sshin	return (1);
21955505Sshin}
22055505Sshin
22155505Sshin/*
22255505Sshin * Tnamatch deals with name matching.  The first field of the termcap
22355505Sshin * entry is a sequence of names separated by |'s, so we compare
22455505Sshin * against each such name.  The normal : terminator after the last
22555505Sshin * name (before the first field) stops us.
22655505Sshin */
22755505Sshinint
228222732Shrstnamatch(char *np)
22955505Sshin{
230118662Sume	char *Np, *Bp;
23155505Sshin
23255505Sshin	Bp = tbuf;
23355505Sshin	if (*Bp == '#')
23455505Sshin		return (0);
23555505Sshin	for (;;) {
23655505Sshin		for (Np = np; *Np && *Bp == *Np; Bp++, Np++)
23755505Sshin			continue;
23855505Sshin		if (*Np == 0 && (*Bp == '|' || *Bp == ':' || *Bp == 0))
23955505Sshin			return (1);
24055505Sshin		while (*Bp && *Bp != ':' && *Bp != '|')
24155505Sshin			Bp++;
24255505Sshin		if (*Bp == 0 || *Bp == ':')
24355505Sshin			return (0);
24455505Sshin		Bp++;
24555505Sshin	}
24655505Sshin}
24755505Sshin
24855505Sshin/*
24955505Sshin * Skip to the next field.  Notice that this is very dumb, not
25055505Sshin * knowing about \: escapes or any such.  If necessary, :'s can be put
25155505Sshin * into the termcap file in octal.
25255505Sshin */
25355505Sshinstatic char *
254222732Shrstskip(char *bp)
25555505Sshin{
25655505Sshin	int dquote;
25755505Sshin
25855505Sshin	dquote = 0;
25955505Sshin	while (*bp) {
26055505Sshin		switch (*bp) {
26155505Sshin		case ':':
26255505Sshin			if (!dquote)
26355505Sshin				goto breakbreak;
26455505Sshin			else
26555505Sshin				bp++;
26655505Sshin			break;
26755505Sshin		case '\\':
26855505Sshin			bp++;
26955505Sshin			if (isdigit(*bp)) {
27055505Sshin				while (isdigit(*bp++))
27155505Sshin					;
27255505Sshin			} else
27355505Sshin				bp++;
27455505Sshin		case '"':
27555505Sshin			dquote = (dquote ? 1 : 0);
27655505Sshin			bp++;
27755505Sshin			break;
27855505Sshin		default:
27955505Sshin			bp++;
28055505Sshin			break;
28155505Sshin		}
28255505Sshin	}
28355505Sshinbreakbreak:
28455505Sshin	if (*bp == ':')
28555505Sshin		bp++;
28655505Sshin	return (bp);
28755505Sshin}
28855505Sshin
28955505Sshin/*
29055505Sshin * Return the (numeric) option id.
29155505Sshin * Numeric options look like
29255505Sshin *	li#80
29355505Sshin * i.e. the option string is separated from the numeric value by
29455505Sshin * a # character.  If the option is not found we return -1.
29555505Sshin * Note that we handle octal numbers beginning with 0.
29655505Sshin */
297118784Sumeint64_t
298222732Shrstgetnum(char *id)
29955505Sshin{
300118784Sume	int64_t i;
301118662Sume	int base;
302118662Sume	char *bp = tbuf;
30355505Sshin
30455505Sshin	for (;;) {
30555505Sshin		bp = tskip(bp);
30655505Sshin		if (*bp == 0)
30755505Sshin			return (-1);
30855505Sshin		if (strncmp(bp, id, strlen(id)) != 0)
30955505Sshin			continue;
31055505Sshin		bp += strlen(id);
31155505Sshin		if (*bp == '@')
31255505Sshin			return (-1);
31355505Sshin		if (*bp != '#')
31455505Sshin			continue;
31555505Sshin		bp++;
31655505Sshin		base = 10;
31755505Sshin		if (*bp == '0')
31855505Sshin			base = 8;
31955505Sshin		i = 0;
32055505Sshin		while (isdigit(*bp))
32155505Sshin			i *= base, i += *bp++ - '0';
32255505Sshin		return (i);
32355505Sshin	}
32455505Sshin}
32555505Sshin
32655505Sshin/*
32755505Sshin * Handle a flag option.
32855505Sshin * Flag options are given "naked", i.e. followed by a : or the end
32955505Sshin * of the buffer.  Return 1 if we find the option, or 0 if it is
33055505Sshin * not given.
33155505Sshin */
33255505Sshinint
333222732Shrstgetflag(char *id)
33455505Sshin{
335118662Sume	char *bp = tbuf;
33655505Sshin
33755505Sshin	for (;;) {
33855505Sshin		bp = tskip(bp);
33955505Sshin		if (!*bp)
34055505Sshin			return (0);
34155505Sshin		if (strncmp(bp, id, strlen(id)) == 0) {
34255505Sshin			bp += strlen(id);
34355505Sshin			if (!*bp || *bp == ':')
34455505Sshin				return (1);
34555505Sshin			else if (*bp == '@')
34655505Sshin				return (0);
34755505Sshin		}
34855505Sshin	}
34955505Sshin}
35055505Sshin
35155505Sshin/*
35255505Sshin * Get a string valued option.
35355505Sshin * These are given as
35455505Sshin *	cl=^Z
35555505Sshin * Much decoding is done on the strings, and the strings are
35655505Sshin * placed in area, which is a ref parameter which is updated.
35755505Sshin * No checking on area overflow.
35855505Sshin */
35955505Sshinchar *
360222732Shrstgetstr(char *id, char **area)
36155505Sshin{
362118662Sume	char *bp = tbuf;
36355505Sshin
36455505Sshin	for (;;) {
36555505Sshin		bp = tskip(bp);
36655505Sshin		if (!*bp)
36755505Sshin			return (0);
36855505Sshin		if (strncmp(bp, id, strlen(id)) != 0)
36955505Sshin			continue;
37055505Sshin		bp += strlen(id);
37155505Sshin		if (*bp == '@')
37255505Sshin			return (0);
37355505Sshin		if (*bp != '=')
37455505Sshin			continue;
37555505Sshin		bp++;
37655505Sshin		return (tdecode(bp, area));
37755505Sshin	}
37855505Sshin}
37955505Sshin
38055505Sshin/*
38155505Sshin * Tdecode does the grung work to decode the
38255505Sshin * string capability escapes.
38355505Sshin */
38455505Sshinstatic char *
385222732Shrstdecode(char *str, char **area)
38655505Sshin{
387118662Sume	char *cp;
388118662Sume	int c;
389222732Shrs	const char *dp;
39055505Sshin	int i;
39155505Sshin	char term;
39255505Sshin
39355505Sshin	term = ':';
39455505Sshin	cp = *area;
39555505Sshinagain:
39655505Sshin	if (*str == '"') {
39755505Sshin		term = '"';
39855505Sshin		str++;
39955505Sshin	}
40055505Sshin	while ((c = *str++) && c != term) {
40155505Sshin		switch (c) {
40255505Sshin
40355505Sshin		case '^':
40455505Sshin			c = *str++ & 037;
40555505Sshin			break;
40655505Sshin
40755505Sshin		case '\\':
40855505Sshin			dp = "E\033^^\\\\::n\nr\rt\tb\bf\f\"\"";
40955505Sshin			c = *str++;
41055505Sshinnextc:
41155505Sshin			if (*dp++ == c) {
41255505Sshin				c = *dp++;
41355505Sshin				break;
41455505Sshin			}
41555505Sshin			dp++;
41655505Sshin			if (*dp)
41755505Sshin				goto nextc;
41855505Sshin			if (isdigit(c)) {
41955505Sshin				c -= '0', i = 2;
42055505Sshin				do
42155505Sshin					c <<= 3, c |= *str++ - '0';
42255505Sshin				while (--i && isdigit(*str));
42355505Sshin			}
42455505Sshin			break;
42555505Sshin		}
42655505Sshin		*cp++ = c;
42755505Sshin	}
42855505Sshin	if (c == term && term != ':') {
42955505Sshin		term = ':';
43055505Sshin		goto again;
43155505Sshin	}
43255505Sshin	*cp++ = 0;
43355505Sshin	str = *area;
43455505Sshin	*area = cp;
43555505Sshin	return (str);
43655505Sshin}
437