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