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 81 static char *tbuf; 82 static int hopcount; /* detect infinite loops in termcap, init 0 */ 83 84 extern const char *conffile; 85 86 int tgetent(char *, char *); 87 int getent(char *, char *, const char *); 88 int tnchktc(void); 89 int tnamatch(char *); 90 static char *tskip(char *); 91 int64_t tgetnum(char *); 92 int tgetflag(char *); 93 char *tgetstr(char *, char **); 94 static 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 */ 101 int 102 tgetent(char *bp, char *name) 103 { 104 return (getent(bp, name, conffile)); 105 } 106 107 int 108 getent(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 */ 178 int 179 tnchktc(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 */ 227 int 228 tnamatch(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 */ 253 static char * 254 tskip(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 } 283 breakbreak: 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 */ 297 int64_t 298 tgetnum(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 */ 332 int 333 tgetflag(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 */ 359 char * 360 tgetstr(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 */ 384 static char * 385 tdecode(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; 395 again: 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++; 410 nextc: 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