|
|
1.1 ! root 1: /* ! 2: * Copyright (c) 1983, 1986 Regents of the University of California. ! 3: * All rights reserved. ! 4: * ! 5: * Redistribution and use in source and binary forms are permitted ! 6: * provided that the above copyright notice and this paragraph are ! 7: * duplicated in all such forms and that any documentation, ! 8: * advertising materials, and other materials related to such ! 9: * distribution and use acknowledge that the software was developed ! 10: * by the University of California, Berkeley. The name of the ! 11: * University may not be used to endorse or promote products derived ! 12: * from this software without specific prior written permission. ! 13: * THIS SOFTWARE IS PROVIDED ``AS IS'' AND WITHOUT ANY EXPRESS OR ! 14: * IMPLIED WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED ! 15: * WARRANTIES OF MERCHANTIBILITY AND FITNESS FOR A PARTICULAR PURPOSE. ! 16: */ ! 17: ! 18: #ifndef lint ! 19: char copyright[] = ! 20: "@(#) Copyright (c) 1983, 1986 Regents of the University of California.\n\ ! 21: All rights reserved.\n"; ! 22: #endif /* not lint */ ! 23: ! 24: #ifndef lint ! 25: static char sccsid[] = "@(#)telnetd.c 5.29 (Berkeley) 6/18/88"; ! 26: #endif /* not lint */ ! 27: ! 28: /* ! 29: * Telnet server. ! 30: */ ! 31: #include <sys/param.h> ! 32: #include <sys/socket.h> ! 33: #include <sys/wait.h> ! 34: #include <sys/file.h> ! 35: #include <sys/stat.h> ! 36: #include <sys/time.h> ! 37: ! 38: #include <netinet/in.h> ! 39: ! 40: #include <arpa/telnet.h> ! 41: ! 42: #include <stdio.h> ! 43: #include <signal.h> ! 44: #include <errno.h> ! 45: #include <sgtty.h> ! 46: #include <netdb.h> ! 47: #include <syslog.h> ! 48: #include <ctype.h> ! 49: ! 50: #define OPT_NO 0 /* won't do this option */ ! 51: #define OPT_YES 1 /* will do this option */ ! 52: #define OPT_YES_BUT_ALWAYS_LOOK 2 ! 53: #define OPT_NO_BUT_ALWAYS_LOOK 3 ! 54: char hisopts[256]; ! 55: char myopts[256]; ! 56: ! 57: char doopt[] = { IAC, DO, '%', 'c', 0 }; ! 58: char dont[] = { IAC, DONT, '%', 'c', 0 }; ! 59: char will[] = { IAC, WILL, '%', 'c', 0 }; ! 60: char wont[] = { IAC, WONT, '%', 'c', 0 }; ! 61: ! 62: /* ! 63: * I/O data buffers, pointers, and counters. ! 64: */ ! 65: char ptyibuf[BUFSIZ], *ptyip = ptyibuf; ! 66: ! 67: char ptyobuf[BUFSIZ], *pfrontp = ptyobuf, *pbackp = ptyobuf; ! 68: ! 69: char netibuf[BUFSIZ], *netip = netibuf; ! 70: #define NIACCUM(c) { *netip++ = c; \ ! 71: ncc++; \ ! 72: } ! 73: ! 74: char netobuf[BUFSIZ], *nfrontp = netobuf, *nbackp = netobuf; ! 75: char *neturg = 0; /* one past last bye of urgent data */ ! 76: /* the remote system seems to NOT be an old 4.2 */ ! 77: int not42 = 1; ! 78: ! 79: #define BANNER "\r\n\r\n4.3 BSD UNIX (%s)\r\n\r\r\n\r" ! 80: ! 81: /* buffer for sub-options */ ! 82: char subbuffer[100], *subpointer= subbuffer, *subend= subbuffer; ! 83: #define SB_CLEAR() subpointer = subbuffer; ! 84: #define SB_TERM() { subend = subpointer; SB_CLEAR(); } ! 85: #define SB_ACCUM(c) if (subpointer < (subbuffer+sizeof subbuffer)) { \ ! 86: *subpointer++ = (c); \ ! 87: } ! 88: #define SB_GET() ((*subpointer++)&0xff) ! 89: #define SB_EOF() (subpointer >= subend) ! 90: ! 91: int pcc, ncc; ! 92: ! 93: int pty, net; ! 94: int inter; ! 95: extern char **environ; ! 96: extern int errno; ! 97: char *line; ! 98: int SYNCHing = 0; /* we are in TELNET SYNCH mode */ ! 99: /* ! 100: * The following are some clocks used to decide how to interpret ! 101: * the relationship between various variables. ! 102: */ ! 103: ! 104: struct { ! 105: int ! 106: system, /* what the current time is */ ! 107: echotoggle, /* last time user entered echo character */ ! 108: modenegotiated, /* last time operating mode negotiated */ ! 109: didnetreceive, /* last time we read data from network */ ! 110: ttypeopt, /* ttype will/won't received */ ! 111: ttypesubopt, /* ttype subopt is received */ ! 112: getterminal, /* time started to get terminal information */ ! 113: gotDM; /* when did we last see a data mark */ ! 114: } clocks; ! 115: ! 116: #define settimer(x) (clocks.x = ++clocks.system) ! 117: #define sequenceIs(x,y) (clocks.x < clocks.y) ! 118: ! 119: main(argc, argv) ! 120: char *argv[]; ! 121: { ! 122: struct sockaddr_in from; ! 123: int on = 1, fromlen; ! 124: ! 125: #if defined(DEBUG) ! 126: { ! 127: int s, ns, foo; ! 128: struct servent *sp; ! 129: static struct sockaddr_in sin = { AF_INET }; ! 130: ! 131: sp = getservbyname("telnet", "tcp"); ! 132: if (sp == 0) { ! 133: fprintf(stderr, "telnetd: tcp/telnet: unknown service\n"); ! 134: exit(1); ! 135: } ! 136: sin.sin_port = sp->s_port; ! 137: argc--, argv++; ! 138: if (argc > 0) { ! 139: sin.sin_port = atoi(*argv); ! 140: sin.sin_port = htons((u_short)sin.sin_port); ! 141: } ! 142: ! 143: s = socket(AF_INET, SOCK_STREAM, 0); ! 144: if (s < 0) { ! 145: perror("telnetd: socket");; ! 146: exit(1); ! 147: } ! 148: if (bind(s, &sin, sizeof sin) < 0) { ! 149: perror("bind"); ! 150: exit(1); ! 151: } ! 152: if (listen(s, 1) < 0) { ! 153: perror("listen"); ! 154: exit(1); ! 155: } ! 156: foo = sizeof sin; ! 157: ns = accept(s, &sin, &foo); ! 158: if (ns < 0) { ! 159: perror("accept"); ! 160: exit(1); ! 161: } ! 162: dup2(ns, 0); ! 163: close(s); ! 164: } ! 165: #endif /* defined(DEBUG) */ ! 166: openlog("telnetd", LOG_PID | LOG_ODELAY, LOG_DAEMON); ! 167: fromlen = sizeof (from); ! 168: if (getpeername(0, &from, &fromlen) < 0) { ! 169: fprintf(stderr, "%s: ", argv[0]); ! 170: perror("getpeername"); ! 171: _exit(1); ! 172: } ! 173: if (setsockopt(0, SOL_SOCKET, SO_KEEPALIVE, &on, sizeof (on)) < 0) { ! 174: syslog(LOG_WARNING, "setsockopt (SO_KEEPALIVE): %m"); ! 175: } ! 176: doit(0, &from); ! 177: } ! 178: ! 179: char *terminaltype = 0; ! 180: char *envinit[2]; ! 181: int cleanup(); ! 182: ! 183: /* ! 184: * ttloop ! 185: * ! 186: * A small subroutine to flush the network output buffer, get some data ! 187: * from the network, and pass it through the telnet state machine. We ! 188: * also flush the pty input buffer (by dropping its data) if it becomes ! 189: * too full. ! 190: */ ! 191: ! 192: void ! 193: ttloop() ! 194: { ! 195: if (nfrontp-nbackp) { ! 196: netflush(); ! 197: } ! 198: ncc = read(net, netibuf, sizeof netibuf); ! 199: if (ncc < 0) { ! 200: syslog(LOG_INFO, "ttloop: read: %m\n"); ! 201: exit(1); ! 202: } else if (ncc == 0) { ! 203: syslog(LOG_INFO, "ttloop: peer died: %m\n"); ! 204: exit(1); ! 205: } ! 206: netip = netibuf; ! 207: telrcv(); /* state machine */ ! 208: if (ncc > 0) { ! 209: pfrontp = pbackp = ptyobuf; ! 210: telrcv(); ! 211: } ! 212: } ! 213: ! 214: /* ! 215: * getterminaltype ! 216: * ! 217: * Ask the other end to send along its terminal type. ! 218: * Output is the variable terminaltype filled in. ! 219: */ ! 220: ! 221: void ! 222: getterminaltype() ! 223: { ! 224: static char sbuf[] = { IAC, DO, TELOPT_TTYPE }; ! 225: ! 226: settimer(getterminal); ! 227: bcopy(sbuf, nfrontp, sizeof sbuf); ! 228: nfrontp += sizeof sbuf; ! 229: hisopts[TELOPT_TTYPE] = OPT_YES_BUT_ALWAYS_LOOK; ! 230: while (sequenceIs(ttypeopt, getterminal)) { ! 231: ttloop(); ! 232: } ! 233: if (hisopts[TELOPT_TTYPE] == OPT_YES) { ! 234: static char sbbuf[] = { IAC, SB, TELOPT_TTYPE, TELQUAL_SEND, IAC, SE }; ! 235: ! 236: bcopy(sbbuf, nfrontp, sizeof sbbuf); ! 237: nfrontp += sizeof sbbuf; ! 238: while (sequenceIs(ttypesubopt, getterminal)) { ! 239: ttloop(); ! 240: } ! 241: } ! 242: } ! 243: ! 244: /* ! 245: * Get a pty, scan input lines. ! 246: */ ! 247: doit(f, who) ! 248: int f; ! 249: struct sockaddr_in *who; ! 250: { ! 251: char *host, *inet_ntoa(); ! 252: int i, p, t; ! 253: struct sgttyb b; ! 254: struct hostent *hp; ! 255: int c; ! 256: ! 257: for (c = 'p'; c <= 's'; c++) { ! 258: struct stat stb; ! 259: ! 260: line = "/dev/ptyXX"; ! 261: line[strlen("/dev/pty")] = c; ! 262: line[strlen("/dev/ptyp")] = '0'; ! 263: if (stat(line, &stb) < 0) ! 264: break; ! 265: for (i = 0; i < 16; i++) { ! 266: line[sizeof("/dev/ptyp") - 1] = "0123456789abcdef"[i]; ! 267: p = open(line, O_RDWR); ! 268: if (p > 0) ! 269: goto gotpty; ! 270: } ! 271: } ! 272: fatal(f, "All network ports in use"); ! 273: /*NOTREACHED*/ ! 274: gotpty: ! 275: dup2(f, 0); ! 276: line[strlen("/dev/")] = 't'; ! 277: t = open("/dev/tty", O_RDWR); ! 278: if (t >= 0) { ! 279: ioctl(t, TIOCNOTTY, 0); ! 280: close(t); ! 281: } ! 282: t = open(line, O_RDWR); ! 283: if (t < 0) ! 284: fatalperror(f, line); ! 285: if (fchmod(t, 0)) ! 286: fatalperror(f, line); ! 287: (void)signal(SIGHUP, SIG_IGN); ! 288: vhangup(); ! 289: (void)signal(SIGHUP, SIG_DFL); ! 290: t = open(line, O_RDWR); ! 291: if (t < 0) ! 292: fatalperror(f, line); ! 293: ioctl(t, TIOCGETP, &b); ! 294: b.sg_flags = CRMOD|XTABS|ANYP; ! 295: ioctl(t, TIOCSETP, &b); ! 296: ioctl(p, TIOCGETP, &b); ! 297: b.sg_flags &= ~ECHO; ! 298: ioctl(p, TIOCSETP, &b); ! 299: hp = gethostbyaddr(&who->sin_addr, sizeof (struct in_addr), ! 300: who->sin_family); ! 301: if (hp) ! 302: host = hp->h_name; ! 303: else ! 304: host = inet_ntoa(who->sin_addr); ! 305: ! 306: net = f; ! 307: pty = p; ! 308: ! 309: /* ! 310: * get terminal type. ! 311: */ ! 312: getterminaltype(); ! 313: ! 314: if ((i = fork()) < 0) ! 315: fatalperror(f, "fork"); ! 316: if (i) ! 317: telnet(f, p); ! 318: close(f); ! 319: close(p); ! 320: dup2(t, 0); ! 321: dup2(t, 1); ! 322: dup2(t, 2); ! 323: close(t); ! 324: envinit[0] = terminaltype; ! 325: envinit[1] = 0; ! 326: environ = envinit; ! 327: /* ! 328: * -h : pass on name of host. ! 329: * WARNING: -h is accepted by login if and only if ! 330: * getuid() == 0. ! 331: * -p : don't clobber the environment (so terminal type stays set). ! 332: */ ! 333: execl("/bin/login", "login", "-h", host, ! 334: terminaltype ? "-p" : 0, 0); ! 335: fatalperror(f, "/bin/login"); ! 336: /*NOTREACHED*/ ! 337: } ! 338: ! 339: fatal(f, msg) ! 340: int f; ! 341: char *msg; ! 342: { ! 343: char buf[BUFSIZ]; ! 344: ! 345: (void) sprintf(buf, "telnetd: %s.\r\n", msg); ! 346: (void) write(f, buf, strlen(buf)); ! 347: exit(1); ! 348: } ! 349: ! 350: fatalperror(f, msg) ! 351: int f; ! 352: char *msg; ! 353: { ! 354: char buf[BUFSIZ]; ! 355: extern char *sys_errlist[]; ! 356: ! 357: (void) sprintf(buf, "%s: %s\r\n", msg, sys_errlist[errno]); ! 358: fatal(f, buf); ! 359: } ! 360: ! 361: ! 362: /* ! 363: * Check a descriptor to see if out of band data exists on it. ! 364: */ ! 365: ! 366: ! 367: stilloob(s) ! 368: int s; /* socket number */ ! 369: { ! 370: static struct timeval timeout = { 0 }; ! 371: fd_set excepts; ! 372: int value; ! 373: ! 374: do { ! 375: FD_ZERO(&excepts); ! 376: FD_SET(s, &excepts); ! 377: value = select(s+1, (fd_set *)0, (fd_set *)0, &excepts, &timeout); ! 378: } while ((value == -1) && (errno == EINTR)); ! 379: ! 380: if (value < 0) { ! 381: fatalperror(pty, "select"); ! 382: } ! 383: if (FD_ISSET(s, &excepts)) { ! 384: return 1; ! 385: } else { ! 386: return 0; ! 387: } ! 388: } ! 389: ! 390: /* ! 391: * Main loop. Select from pty and network, and ! 392: * hand data to telnet receiver finite state machine. ! 393: */ ! 394: telnet(f, p) ! 395: { ! 396: int on = 1; ! 397: char hostname[MAXHOSTNAMELEN]; ! 398: #define TABBUFSIZ 512 ! 399: char defent[TABBUFSIZ]; ! 400: char defstrs[TABBUFSIZ]; ! 401: #undef TABBUFSIZ ! 402: char *HE; ! 403: char *HN; ! 404: char *IM; ! 405: ! 406: ioctl(f, FIONBIO, &on); ! 407: ioctl(p, FIONBIO, &on); ! 408: ioctl(p, TIOCPKT, &on); ! 409: #if defined(SO_OOBINLINE) ! 410: setsockopt(net, SOL_SOCKET, SO_OOBINLINE, &on, sizeof on); ! 411: #endif /* defined(SO_OOBINLINE) */ ! 412: signal(SIGTSTP, SIG_IGN); ! 413: /* ! 414: * Ignoring SIGTTOU keeps the kernel from blocking us ! 415: * in ttioctl() in /sys/tty.c. ! 416: */ ! 417: signal(SIGTTOU, SIG_IGN); ! 418: signal(SIGCHLD, cleanup); ! 419: setpgrp(0, 0); ! 420: ! 421: /* ! 422: * Request to do remote echo and to suppress go ahead. ! 423: */ ! 424: if (!myopts[TELOPT_ECHO]) { ! 425: dooption(TELOPT_ECHO); ! 426: } ! 427: if (!myopts[TELOPT_SGA]) { ! 428: dooption(TELOPT_SGA); ! 429: } ! 430: /* ! 431: * Is the client side a 4.2 (NOT 4.3) system? We need to know this ! 432: * because 4.2 clients are unable to deal with TCP urgent data. ! 433: * ! 434: * To find out, we send out a "DO ECHO". If the remote system ! 435: * answers "WILL ECHO" it is probably a 4.2 client, and we note ! 436: * that fact ("WILL ECHO" ==> that the client will echo what ! 437: * WE, the server, sends it; it does NOT mean that the client will ! 438: * echo the terminal input). ! 439: */ ! 440: (void) sprintf(nfrontp, doopt, TELOPT_ECHO); ! 441: nfrontp += sizeof doopt-2; ! 442: hisopts[TELOPT_ECHO] = OPT_YES_BUT_ALWAYS_LOOK; ! 443: ! 444: /* ! 445: * Show banner that getty never gave. ! 446: * ! 447: * We put the banner in the pty input buffer. This way, it ! 448: * gets carriage return null processing, etc., just like all ! 449: * other pty --> client data. ! 450: */ ! 451: ! 452: gethostname(hostname, sizeof (hostname)); ! 453: if (getent(defent, "default") == 1) { ! 454: char *getstr(); ! 455: char *p=defstrs; ! 456: ! 457: HE = getstr("he", &p); ! 458: HN = getstr("hn", &p); ! 459: IM = getstr("im", &p); ! 460: if (HN && *HN) ! 461: strcpy(hostname, HN); ! 462: edithost(HE, hostname); ! 463: if (IM && *IM) ! 464: putf(IM, ptyibuf+1); ! 465: } else { ! 466: sprintf(ptyibuf+1, BANNER, hostname); ! 467: } ! 468: ! 469: ptyip = ptyibuf+1; /* Prime the pump */ ! 470: pcc = strlen(ptyip); /* ditto */ ! 471: ! 472: /* Clear ptybuf[0] - where the packet information is received */ ! 473: ptyibuf[0] = 0; ! 474: ! 475: /* ! 476: * Call telrcv() once to pick up anything received during ! 477: * terminal type negotiation. ! 478: */ ! 479: telrcv(); ! 480: ! 481: for (;;) { ! 482: fd_set ibits, obits, xbits; ! 483: register int c; ! 484: ! 485: if (ncc < 0 && pcc < 0) ! 486: break; ! 487: ! 488: FD_ZERO(&ibits); ! 489: FD_ZERO(&obits); ! 490: FD_ZERO(&xbits); ! 491: /* ! 492: * Never look for input if there's still ! 493: * stuff in the corresponding output buffer ! 494: */ ! 495: if (nfrontp - nbackp || pcc > 0) { ! 496: FD_SET(f, &obits); ! 497: FD_SET(p, &xbits); ! 498: } else { ! 499: FD_SET(p, &ibits); ! 500: } ! 501: if (pfrontp - pbackp || ncc > 0) { ! 502: FD_SET(p, &obits); ! 503: } else { ! 504: FD_SET(f, &ibits); ! 505: } ! 506: if (!SYNCHing) { ! 507: FD_SET(f, &xbits); ! 508: } ! 509: if ((c = select(16, &ibits, &obits, &xbits, ! 510: (struct timeval *)0)) < 1) { ! 511: if (c == -1) { ! 512: if (errno == EINTR) { ! 513: continue; ! 514: } ! 515: } ! 516: sleep(5); ! 517: continue; ! 518: } ! 519: ! 520: /* ! 521: * Any urgent data? ! 522: */ ! 523: if (FD_ISSET(net, &xbits)) { ! 524: SYNCHing = 1; ! 525: } ! 526: ! 527: /* ! 528: * Something to read from the network... ! 529: */ ! 530: if (FD_ISSET(net, &ibits)) { ! 531: #if !defined(SO_OOBINLINE) ! 532: /* ! 533: * In 4.2 (and 4.3 beta) systems, the ! 534: * OOB indication and data handling in the kernel ! 535: * is such that if two separate TCP Urgent requests ! 536: * come in, one byte of TCP data will be overlaid. ! 537: * This is fatal for Telnet, but we try to live ! 538: * with it. ! 539: * ! 540: * In addition, in 4.2 (and...), a special protocol ! 541: * is needed to pick up the TCP Urgent data in ! 542: * the correct sequence. ! 543: * ! 544: * What we do is: if we think we are in urgent ! 545: * mode, we look to see if we are "at the mark". ! 546: * If we are, we do an OOB receive. If we run ! 547: * this twice, we will do the OOB receive twice, ! 548: * but the second will fail, since the second ! 549: * time we were "at the mark", but there wasn't ! 550: * any data there (the kernel doesn't reset ! 551: * "at the mark" until we do a normal read). ! 552: * Once we've read the OOB data, we go ahead ! 553: * and do normal reads. ! 554: * ! 555: * There is also another problem, which is that ! 556: * since the OOB byte we read doesn't put us ! 557: * out of OOB state, and since that byte is most ! 558: * likely the TELNET DM (data mark), we would ! 559: * stay in the TELNET SYNCH (SYNCHing) state. ! 560: * So, clocks to the rescue. If we've "just" ! 561: * received a DM, then we test for the ! 562: * presence of OOB data when the receive OOB ! 563: * fails (and AFTER we did the normal mode read ! 564: * to clear "at the mark"). ! 565: */ ! 566: if (SYNCHing) { ! 567: int atmark; ! 568: ! 569: ioctl(net, SIOCATMARK, (char *)&atmark); ! 570: if (atmark) { ! 571: ncc = recv(net, netibuf, sizeof (netibuf), MSG_OOB); ! 572: if ((ncc == -1) && (errno == EINVAL)) { ! 573: ncc = read(net, netibuf, sizeof (netibuf)); ! 574: if (sequenceIs(didnetreceive, gotDM)) { ! 575: SYNCHing = stilloob(net); ! 576: } ! 577: } ! 578: } else { ! 579: ncc = read(net, netibuf, sizeof (netibuf)); ! 580: } ! 581: } else { ! 582: ncc = read(net, netibuf, sizeof (netibuf)); ! 583: } ! 584: settimer(didnetreceive); ! 585: #else /* !defined(SO_OOBINLINE)) */ ! 586: ncc = read(net, netibuf, sizeof (netibuf)); ! 587: #endif /* !defined(SO_OOBINLINE)) */ ! 588: if (ncc < 0 && errno == EWOULDBLOCK) ! 589: ncc = 0; ! 590: else { ! 591: if (ncc <= 0) { ! 592: break; ! 593: } ! 594: netip = netibuf; ! 595: } ! 596: } ! 597: ! 598: /* ! 599: * Something to read from the pty... ! 600: */ ! 601: if (FD_ISSET(p, &xbits)) { ! 602: if (read(p, ptyibuf, 1) != 1) { ! 603: break; ! 604: } ! 605: } ! 606: if (FD_ISSET(p, &ibits)) { ! 607: pcc = read(p, ptyibuf, BUFSIZ); ! 608: if (pcc < 0 && errno == EWOULDBLOCK) ! 609: pcc = 0; ! 610: else { ! 611: if (pcc <= 0) ! 612: break; ! 613: /* Skip past "packet" */ ! 614: pcc--; ! 615: ptyip = ptyibuf+1; ! 616: } ! 617: } ! 618: if (ptyibuf[0] & TIOCPKT_FLUSHWRITE) { ! 619: netclear(); /* clear buffer back */ ! 620: *nfrontp++ = IAC; ! 621: *nfrontp++ = DM; ! 622: neturg = nfrontp-1; /* off by one XXX */ ! 623: ptyibuf[0] = 0; ! 624: } ! 625: ! 626: while (pcc > 0) { ! 627: if ((&netobuf[BUFSIZ] - nfrontp) < 2) ! 628: break; ! 629: c = *ptyip++ & 0377, pcc--; ! 630: if (c == IAC) ! 631: *nfrontp++ = c; ! 632: *nfrontp++ = c; ! 633: /* Don't do CR-NUL if we are in binary mode */ ! 634: if ((c == '\r') && (myopts[TELOPT_BINARY] == OPT_NO)) { ! 635: if (pcc > 0 && ((*ptyip & 0377) == '\n')) { ! 636: *nfrontp++ = *ptyip++ & 0377; ! 637: pcc--; ! 638: } else ! 639: *nfrontp++ = '\0'; ! 640: } ! 641: } ! 642: if (FD_ISSET(f, &obits) && (nfrontp - nbackp) > 0) ! 643: netflush(); ! 644: if (ncc > 0) ! 645: telrcv(); ! 646: if (FD_ISSET(p, &obits) && (pfrontp - pbackp) > 0) ! 647: ptyflush(); ! 648: } ! 649: cleanup(); ! 650: } ! 651: ! 652: /* ! 653: * State for recv fsm ! 654: */ ! 655: #define TS_DATA 0 /* base state */ ! 656: #define TS_IAC 1 /* look for double IAC's */ ! 657: #define TS_CR 2 /* CR-LF ->'s CR */ ! 658: #define TS_SB 3 /* throw away begin's... */ ! 659: #define TS_SE 4 /* ...end's (suboption negotiation) */ ! 660: #define TS_WILL 5 /* will option negotiation */ ! 661: #define TS_WONT 6 /* wont " */ ! 662: #define TS_DO 7 /* do " */ ! 663: #define TS_DONT 8 /* dont " */ ! 664: ! 665: telrcv() ! 666: { ! 667: register int c; ! 668: static int state = TS_DATA; ! 669: ! 670: while (ncc > 0) { ! 671: if ((&ptyobuf[BUFSIZ] - pfrontp) < 2) ! 672: return; ! 673: c = *netip++ & 0377, ncc--; ! 674: switch (state) { ! 675: ! 676: case TS_CR: ! 677: state = TS_DATA; ! 678: /* Strip off \n or \0 after a \r */ ! 679: if ((c == 0) || (c == '\n')) { ! 680: break; ! 681: } ! 682: /* FALL THROUGH */ ! 683: ! 684: case TS_DATA: ! 685: if (c == IAC) { ! 686: state = TS_IAC; ! 687: break; ! 688: } ! 689: if (inter > 0) ! 690: break; ! 691: /* ! 692: * We now map \r\n ==> \r for pragmatic reasons. ! 693: * Many client implementations send \r\n when ! 694: * the user hits the CarriageReturn key. ! 695: * ! 696: * We USED to map \r\n ==> \n, since \r\n says ! 697: * that we want to be in column 1 of the next ! 698: * printable line, and \n is the standard ! 699: * unix way of saying that (\r is only good ! 700: * if CRMOD is set, which it normally is). ! 701: */ ! 702: if ((c == '\r') && (hisopts[TELOPT_BINARY] == OPT_NO)) { ! 703: state = TS_CR; ! 704: } ! 705: *pfrontp++ = c; ! 706: break; ! 707: ! 708: case TS_IAC: ! 709: switch (c) { ! 710: ! 711: /* ! 712: * Send the process on the pty side an ! 713: * interrupt. Do this with a NULL or ! 714: * interrupt char; depending on the tty mode. ! 715: */ ! 716: case IP: ! 717: interrupt(); ! 718: break; ! 719: ! 720: case BREAK: ! 721: sendbrk(); ! 722: break; ! 723: ! 724: /* ! 725: * Are You There? ! 726: */ ! 727: case AYT: ! 728: strcpy(nfrontp, "\r\n[Yes]\r\n"); ! 729: nfrontp += 9; ! 730: break; ! 731: ! 732: /* ! 733: * Abort Output ! 734: */ ! 735: case AO: { ! 736: struct ltchars tmpltc; ! 737: ! 738: ptyflush(); /* half-hearted */ ! 739: ioctl(pty, TIOCGLTC, &tmpltc); ! 740: if (tmpltc.t_flushc != '\377') { ! 741: *pfrontp++ = tmpltc.t_flushc; ! 742: } ! 743: netclear(); /* clear buffer back */ ! 744: *nfrontp++ = IAC; ! 745: *nfrontp++ = DM; ! 746: neturg = nfrontp-1; /* off by one XXX */ ! 747: break; ! 748: } ! 749: ! 750: /* ! 751: * Erase Character and ! 752: * Erase Line ! 753: */ ! 754: case EC: ! 755: case EL: { ! 756: struct sgttyb b; ! 757: char ch; ! 758: ! 759: ptyflush(); /* half-hearted */ ! 760: ioctl(pty, TIOCGETP, &b); ! 761: ch = (c == EC) ? ! 762: b.sg_erase : b.sg_kill; ! 763: if (ch != '\377') { ! 764: *pfrontp++ = ch; ! 765: } ! 766: break; ! 767: } ! 768: ! 769: /* ! 770: * Check for urgent data... ! 771: */ ! 772: case DM: ! 773: SYNCHing = stilloob(net); ! 774: settimer(gotDM); ! 775: break; ! 776: ! 777: ! 778: /* ! 779: * Begin option subnegotiation... ! 780: */ ! 781: case SB: ! 782: state = TS_SB; ! 783: continue; ! 784: ! 785: case WILL: ! 786: state = TS_WILL; ! 787: continue; ! 788: ! 789: case WONT: ! 790: state = TS_WONT; ! 791: continue; ! 792: ! 793: case DO: ! 794: state = TS_DO; ! 795: continue; ! 796: ! 797: case DONT: ! 798: state = TS_DONT; ! 799: continue; ! 800: ! 801: case IAC: ! 802: *pfrontp++ = c; ! 803: break; ! 804: } ! 805: state = TS_DATA; ! 806: break; ! 807: ! 808: case TS_SB: ! 809: if (c == IAC) { ! 810: state = TS_SE; ! 811: } else { ! 812: SB_ACCUM(c); ! 813: } ! 814: break; ! 815: ! 816: case TS_SE: ! 817: if (c != SE) { ! 818: if (c != IAC) { ! 819: SB_ACCUM(IAC); ! 820: } ! 821: SB_ACCUM(c); ! 822: state = TS_SB; ! 823: } else { ! 824: SB_TERM(); ! 825: suboption(); /* handle sub-option */ ! 826: state = TS_DATA; ! 827: } ! 828: break; ! 829: ! 830: case TS_WILL: ! 831: if (hisopts[c] != OPT_YES) ! 832: willoption(c); ! 833: state = TS_DATA; ! 834: continue; ! 835: ! 836: case TS_WONT: ! 837: if (hisopts[c] != OPT_NO) ! 838: wontoption(c); ! 839: state = TS_DATA; ! 840: continue; ! 841: ! 842: case TS_DO: ! 843: if (myopts[c] != OPT_YES) ! 844: dooption(c); ! 845: state = TS_DATA; ! 846: continue; ! 847: ! 848: case TS_DONT: ! 849: if (myopts[c] != OPT_NO) { ! 850: dontoption(c); ! 851: } ! 852: state = TS_DATA; ! 853: continue; ! 854: ! 855: default: ! 856: syslog(LOG_ERR, "telnetd: panic state=%d\n", state); ! 857: printf("telnetd: panic state=%d\n", state); ! 858: exit(1); ! 859: } ! 860: } ! 861: } ! 862: ! 863: willoption(option) ! 864: int option; ! 865: { ! 866: char *fmt; ! 867: ! 868: switch (option) { ! 869: ! 870: case TELOPT_BINARY: ! 871: mode(RAW, 0); ! 872: fmt = doopt; ! 873: break; ! 874: ! 875: case TELOPT_ECHO: ! 876: not42 = 0; /* looks like a 4.2 system */ ! 877: /* ! 878: * Now, in a 4.2 system, to break them out of ECHOing ! 879: * (to the terminal) mode, we need to send a "WILL ECHO". ! 880: * Kludge upon kludge! ! 881: */ ! 882: if (myopts[TELOPT_ECHO] == OPT_YES) { ! 883: dooption(TELOPT_ECHO); ! 884: } ! 885: fmt = dont; ! 886: break; ! 887: ! 888: case TELOPT_TTYPE: ! 889: settimer(ttypeopt); ! 890: if (hisopts[TELOPT_TTYPE] == OPT_YES_BUT_ALWAYS_LOOK) { ! 891: hisopts[TELOPT_TTYPE] = OPT_YES; ! 892: return; ! 893: } ! 894: fmt = doopt; ! 895: break; ! 896: ! 897: case TELOPT_SGA: ! 898: fmt = doopt; ! 899: break; ! 900: ! 901: case TELOPT_TM: ! 902: fmt = dont; ! 903: break; ! 904: ! 905: default: ! 906: fmt = dont; ! 907: break; ! 908: } ! 909: if (fmt == doopt) { ! 910: hisopts[option] = OPT_YES; ! 911: } else { ! 912: hisopts[option] = OPT_NO; ! 913: } ! 914: (void) sprintf(nfrontp, fmt, option); ! 915: nfrontp += sizeof (dont) - 2; ! 916: } ! 917: ! 918: wontoption(option) ! 919: int option; ! 920: { ! 921: char *fmt; ! 922: ! 923: switch (option) { ! 924: case TELOPT_ECHO: ! 925: not42 = 1; /* doesn't seem to be a 4.2 system */ ! 926: break; ! 927: ! 928: case TELOPT_BINARY: ! 929: mode(0, RAW); ! 930: break; ! 931: ! 932: case TELOPT_TTYPE: ! 933: settimer(ttypeopt); ! 934: break; ! 935: } ! 936: ! 937: fmt = dont; ! 938: hisopts[option] = OPT_NO; ! 939: (void) sprintf(nfrontp, fmt, option); ! 940: nfrontp += sizeof (doopt) - 2; ! 941: } ! 942: ! 943: dooption(option) ! 944: int option; ! 945: { ! 946: char *fmt; ! 947: ! 948: switch (option) { ! 949: ! 950: case TELOPT_TM: ! 951: fmt = wont; ! 952: break; ! 953: ! 954: case TELOPT_ECHO: ! 955: mode(ECHO|CRMOD, 0); ! 956: fmt = will; ! 957: break; ! 958: ! 959: case TELOPT_BINARY: ! 960: mode(RAW, 0); ! 961: fmt = will; ! 962: break; ! 963: ! 964: case TELOPT_SGA: ! 965: fmt = will; ! 966: break; ! 967: ! 968: default: ! 969: fmt = wont; ! 970: break; ! 971: } ! 972: if (fmt == will) { ! 973: myopts[option] = OPT_YES; ! 974: } else { ! 975: myopts[option] = OPT_NO; ! 976: } ! 977: (void) sprintf(nfrontp, fmt, option); ! 978: nfrontp += sizeof (doopt) - 2; ! 979: } ! 980: ! 981: ! 982: dontoption(option) ! 983: int option; ! 984: { ! 985: char *fmt; ! 986: ! 987: switch (option) { ! 988: case TELOPT_ECHO: /* we should stop echoing */ ! 989: mode(0, ECHO); ! 990: fmt = wont; ! 991: break; ! 992: ! 993: default: ! 994: fmt = wont; ! 995: break; ! 996: } ! 997: ! 998: if (fmt = wont) { ! 999: myopts[option] = OPT_NO; ! 1000: } else { ! 1001: myopts[option] = OPT_YES; ! 1002: } ! 1003: (void) sprintf(nfrontp, fmt, option); ! 1004: nfrontp += sizeof (wont) - 2; ! 1005: } ! 1006: ! 1007: /* ! 1008: * suboption() ! 1009: * ! 1010: * Look at the sub-option buffer, and try to be helpful to the other ! 1011: * side. ! 1012: * ! 1013: * Currently we recognize: ! 1014: * ! 1015: * Terminal type is ! 1016: */ ! 1017: ! 1018: suboption() ! 1019: { ! 1020: switch (SB_GET()) { ! 1021: case TELOPT_TTYPE: { /* Yaaaay! */ ! 1022: static char terminalname[5+41] = "TERM="; ! 1023: ! 1024: settimer(ttypesubopt); ! 1025: ! 1026: if (SB_GET() != TELQUAL_IS) { ! 1027: return; /* ??? XXX but, this is the most robust */ ! 1028: } ! 1029: ! 1030: terminaltype = terminalname+strlen(terminalname); ! 1031: ! 1032: while ((terminaltype < (terminalname + sizeof terminalname-1)) && ! 1033: !SB_EOF()) { ! 1034: register int c; ! 1035: ! 1036: c = SB_GET(); ! 1037: if (isupper(c)) { ! 1038: c = tolower(c); ! 1039: } ! 1040: *terminaltype++ = c; /* accumulate name */ ! 1041: } ! 1042: *terminaltype = 0; ! 1043: terminaltype = terminalname; ! 1044: break; ! 1045: } ! 1046: ! 1047: default: ! 1048: ; ! 1049: } ! 1050: } ! 1051: ! 1052: mode(on, off) ! 1053: int on, off; ! 1054: { ! 1055: struct sgttyb b; ! 1056: ! 1057: ptyflush(); ! 1058: ioctl(pty, TIOCGETP, &b); ! 1059: b.sg_flags |= on; ! 1060: b.sg_flags &= ~off; ! 1061: ioctl(pty, TIOCSETP, &b); ! 1062: } ! 1063: ! 1064: /* ! 1065: * Send interrupt to process on other side of pty. ! 1066: * If it is in raw mode, just write NULL; ! 1067: * otherwise, write intr char. ! 1068: */ ! 1069: interrupt() ! 1070: { ! 1071: struct sgttyb b; ! 1072: struct tchars tchars; ! 1073: ! 1074: ptyflush(); /* half-hearted */ ! 1075: ioctl(pty, TIOCGETP, &b); ! 1076: if (b.sg_flags & RAW) { ! 1077: *pfrontp++ = '\0'; ! 1078: return; ! 1079: } ! 1080: *pfrontp++ = ioctl(pty, TIOCGETC, &tchars) < 0 ? ! 1081: '\177' : tchars.t_intrc; ! 1082: } ! 1083: ! 1084: /* ! 1085: * Send quit to process on other side of pty. ! 1086: * If it is in raw mode, just write NULL; ! 1087: * otherwise, write quit char. ! 1088: */ ! 1089: sendbrk() ! 1090: { ! 1091: struct sgttyb b; ! 1092: struct tchars tchars; ! 1093: ! 1094: ptyflush(); /* half-hearted */ ! 1095: ioctl(pty, TIOCGETP, &b); ! 1096: if (b.sg_flags & RAW) { ! 1097: *pfrontp++ = '\0'; ! 1098: return; ! 1099: } ! 1100: *pfrontp++ = ioctl(pty, TIOCGETC, &tchars) < 0 ? ! 1101: '\034' : tchars.t_quitc; ! 1102: } ! 1103: ! 1104: ptyflush() ! 1105: { ! 1106: int n; ! 1107: ! 1108: if ((n = pfrontp - pbackp) > 0) ! 1109: n = write(pty, pbackp, n); ! 1110: if (n < 0) ! 1111: return; ! 1112: pbackp += n; ! 1113: if (pbackp == pfrontp) ! 1114: pbackp = pfrontp = ptyobuf; ! 1115: } ! 1116: ! 1117: /* ! 1118: * nextitem() ! 1119: * ! 1120: * Return the address of the next "item" in the TELNET data ! 1121: * stream. This will be the address of the next character if ! 1122: * the current address is a user data character, or it will ! 1123: * be the address of the character following the TELNET command ! 1124: * if the current address is a TELNET IAC ("I Am a Command") ! 1125: * character. ! 1126: */ ! 1127: ! 1128: char * ! 1129: nextitem(current) ! 1130: char *current; ! 1131: { ! 1132: if ((*current&0xff) != IAC) { ! 1133: return current+1; ! 1134: } ! 1135: switch (*(current+1)&0xff) { ! 1136: case DO: ! 1137: case DONT: ! 1138: case WILL: ! 1139: case WONT: ! 1140: return current+3; ! 1141: case SB: /* loop forever looking for the SE */ ! 1142: { ! 1143: register char *look = current+2; ! 1144: ! 1145: for (;;) { ! 1146: if ((*look++&0xff) == IAC) { ! 1147: if ((*look++&0xff) == SE) { ! 1148: return look; ! 1149: } ! 1150: } ! 1151: } ! 1152: } ! 1153: default: ! 1154: return current+2; ! 1155: } ! 1156: } ! 1157: ! 1158: ! 1159: /* ! 1160: * netclear() ! 1161: * ! 1162: * We are about to do a TELNET SYNCH operation. Clear ! 1163: * the path to the network. ! 1164: * ! 1165: * Things are a bit tricky since we may have sent the first ! 1166: * byte or so of a previous TELNET command into the network. ! 1167: * So, we have to scan the network buffer from the beginning ! 1168: * until we are up to where we want to be. ! 1169: * ! 1170: * A side effect of what we do, just to keep things ! 1171: * simple, is to clear the urgent data pointer. The principal ! 1172: * caller should be setting the urgent data pointer AFTER calling ! 1173: * us in any case. ! 1174: */ ! 1175: ! 1176: netclear() ! 1177: { ! 1178: register char *thisitem, *next; ! 1179: char *good; ! 1180: #define wewant(p) ((nfrontp > p) && ((*p&0xff) == IAC) && \ ! 1181: ((*(p+1)&0xff) != EC) && ((*(p+1)&0xff) != EL)) ! 1182: ! 1183: thisitem = netobuf; ! 1184: ! 1185: while ((next = nextitem(thisitem)) <= nbackp) { ! 1186: thisitem = next; ! 1187: } ! 1188: ! 1189: /* Now, thisitem is first before/at boundary. */ ! 1190: ! 1191: good = netobuf; /* where the good bytes go */ ! 1192: ! 1193: while (nfrontp > thisitem) { ! 1194: if (wewant(thisitem)) { ! 1195: int length; ! 1196: ! 1197: next = thisitem; ! 1198: do { ! 1199: next = nextitem(next); ! 1200: } while (wewant(next) && (nfrontp > next)); ! 1201: length = next-thisitem; ! 1202: bcopy(thisitem, good, length); ! 1203: good += length; ! 1204: thisitem = next; ! 1205: } else { ! 1206: thisitem = nextitem(thisitem); ! 1207: } ! 1208: } ! 1209: ! 1210: nbackp = netobuf; ! 1211: nfrontp = good; /* next byte to be sent */ ! 1212: neturg = 0; ! 1213: } ! 1214: ! 1215: /* ! 1216: * netflush ! 1217: * Send as much data as possible to the network, ! 1218: * handling requests for urgent data. ! 1219: */ ! 1220: ! 1221: ! 1222: netflush() ! 1223: { ! 1224: int n; ! 1225: ! 1226: if ((n = nfrontp - nbackp) > 0) { ! 1227: /* ! 1228: * if no urgent data, or if the other side appears to be an ! 1229: * old 4.2 client (and thus unable to survive TCP urgent data), ! 1230: * write the entire buffer in non-OOB mode. ! 1231: */ ! 1232: if ((neturg == 0) || (not42 == 0)) { ! 1233: n = write(net, nbackp, n); /* normal write */ ! 1234: } else { ! 1235: n = neturg - nbackp; ! 1236: /* ! 1237: * In 4.2 (and 4.3) systems, there is some question about ! 1238: * what byte in a sendOOB operation is the "OOB" data. ! 1239: * To make ourselves compatible, we only send ONE byte ! 1240: * out of band, the one WE THINK should be OOB (though ! 1241: * we really have more the TCP philosophy of urgent data ! 1242: * rather than the Unix philosophy of OOB data). ! 1243: */ ! 1244: if (n > 1) { ! 1245: n = send(net, nbackp, n-1, 0); /* send URGENT all by itself */ ! 1246: } else { ! 1247: n = send(net, nbackp, n, MSG_OOB); /* URGENT data */ ! 1248: } ! 1249: } ! 1250: } ! 1251: if (n < 0) { ! 1252: if (errno == EWOULDBLOCK) ! 1253: return; ! 1254: /* should blow this guy away... */ ! 1255: return; ! 1256: } ! 1257: nbackp += n; ! 1258: if (nbackp >= neturg) { ! 1259: neturg = 0; ! 1260: } ! 1261: if (nbackp == nfrontp) { ! 1262: nbackp = nfrontp = netobuf; ! 1263: } ! 1264: } ! 1265: ! 1266: cleanup() ! 1267: { ! 1268: ! 1269: rmut(); ! 1270: shutdown(net, 2); ! 1271: exit(1); ! 1272: } ! 1273: ! 1274: #include <utmp.h> ! 1275: ! 1276: struct utmp wtmp; ! 1277: char wtmpf[] = "/usr/adm/wtmp"; ! 1278: char utmpf[] = "/etc/utmp"; ! 1279: #define SCPYN(a, b) strncpy(a, b, sizeof(a)) ! 1280: #define SCMPN(a, b) strncmp(a, b, sizeof(a)) ! 1281: ! 1282: rmut() ! 1283: { ! 1284: register f; ! 1285: int found = 0; ! 1286: struct utmp *u, *utmp; ! 1287: int nutmp; ! 1288: struct stat statbf; ! 1289: ! 1290: f = open(utmpf, O_RDWR); ! 1291: if (f >= 0) { ! 1292: fstat(f, &statbf); ! 1293: utmp = (struct utmp *)malloc(statbf.st_size); ! 1294: if (!utmp) ! 1295: syslog(LOG_ERR, "utmp malloc failed"); ! 1296: if (statbf.st_size && utmp) { ! 1297: nutmp = read(f, utmp, statbf.st_size); ! 1298: nutmp /= sizeof(struct utmp); ! 1299: ! 1300: for (u = utmp ; u < &utmp[nutmp] ; u++) { ! 1301: if (SCMPN(u->ut_line, line+5) || ! 1302: u->ut_name[0]==0) ! 1303: continue; ! 1304: lseek(f, ((long)u)-((long)utmp), L_SET); ! 1305: SCPYN(u->ut_name, ""); ! 1306: SCPYN(u->ut_host, ""); ! 1307: time(&u->ut_time); ! 1308: write(f, (char *)u, sizeof(wtmp)); ! 1309: found++; ! 1310: } ! 1311: } ! 1312: close(f); ! 1313: } ! 1314: if (found) { ! 1315: f = open(wtmpf, O_WRONLY|O_APPEND); ! 1316: if (f >= 0) { ! 1317: SCPYN(wtmp.ut_line, line+5); ! 1318: SCPYN(wtmp.ut_name, ""); ! 1319: SCPYN(wtmp.ut_host, ""); ! 1320: time(&wtmp.ut_time); ! 1321: write(f, (char *)&wtmp, sizeof(wtmp)); ! 1322: close(f); ! 1323: } ! 1324: } ! 1325: chmod(line, 0666); ! 1326: chown(line, 0, 0); ! 1327: line[strlen("/dev/")] = 'p'; ! 1328: chmod(line, 0666); ! 1329: chown(line, 0, 0); ! 1330: } ! 1331: ! 1332: char editedhost[32]; ! 1333: ! 1334: edithost(pat, host) ! 1335: register char *pat; ! 1336: register char *host; ! 1337: { ! 1338: register char *res = editedhost; ! 1339: ! 1340: if (!pat) ! 1341: pat = ""; ! 1342: while (*pat) { ! 1343: switch (*pat) { ! 1344: ! 1345: case '#': ! 1346: if (*host) ! 1347: host++; ! 1348: break; ! 1349: ! 1350: case '@': ! 1351: if (*host) ! 1352: *res++ = *host++; ! 1353: break; ! 1354: ! 1355: default: ! 1356: *res++ = *pat; ! 1357: break; ! 1358: ! 1359: } ! 1360: if (res == &editedhost[sizeof editedhost - 1]) { ! 1361: *res = '\0'; ! 1362: return; ! 1363: } ! 1364: pat++; ! 1365: } ! 1366: if (*host) ! 1367: strncpy(res, host, sizeof editedhost - (res - editedhost) - 1); ! 1368: else ! 1369: *res = '\0'; ! 1370: editedhost[sizeof editedhost - 1] = '\0'; ! 1371: } ! 1372: ! 1373: static char *putlocation; ! 1374: ! 1375: puts(s) ! 1376: register char *s; ! 1377: { ! 1378: ! 1379: while (*s) ! 1380: putchr(*s++); ! 1381: } ! 1382: ! 1383: putchr(cc) ! 1384: { ! 1385: *putlocation++ = cc; ! 1386: } ! 1387: ! 1388: putf(cp, where) ! 1389: register char *cp; ! 1390: char *where; ! 1391: { ! 1392: char *slash; ! 1393: char datebuffer[60]; ! 1394: extern char *rindex(); ! 1395: ! 1396: putlocation = where; ! 1397: ! 1398: while (*cp) { ! 1399: if (*cp != '%') { ! 1400: putchr(*cp++); ! 1401: continue; ! 1402: } ! 1403: switch (*++cp) { ! 1404: ! 1405: case 't': ! 1406: slash = rindex(line, '/'); ! 1407: if (slash == (char *) 0) ! 1408: puts(line); ! 1409: else ! 1410: puts(&slash[1]); ! 1411: break; ! 1412: ! 1413: case 'h': ! 1414: puts(editedhost); ! 1415: break; ! 1416: ! 1417: case 'd': ! 1418: get_date(datebuffer); ! 1419: puts(datebuffer); ! 1420: break; ! 1421: ! 1422: case '%': ! 1423: putchr('%'); ! 1424: break; ! 1425: } ! 1426: cp++; ! 1427: } ! 1428: }
This archive runs on limited infrastructure. Preserving old code on modern bandwidth. Automated agents are requested to crawl responsibly.