|
|
1.1 ! root 1: #ifndef lint ! 2: static char sccsid[] = "@(#)telnet.c 4.24 (Berkeley) 7/20/83"; ! 3: #endif ! 4: ! 5: /* ! 6: * User telnet program. ! 7: */ ! 8: #include <sys/param.h> ! 9: #include <sys/ttyio.h> ! 10: #include <sys/nttyio.h> ! 11: ! 12: #define TELOPTS ! 13: #include "telnet.h" ! 14: ! 15: #include <stdio.h> ! 16: #include <ctype.h> ! 17: #include <errno.h> ! 18: #include <signal.h> ! 19: #include <setjmp.h> ! 20: #include <ipc.h> ! 21: #include <libc.h> ! 22: ! 23: #define strip(x) ((x)&0177) ! 24: ! 25: char ttyobuf[BUFSIZ], *tfrontp = ttyobuf, *tbackp = ttyobuf; ! 26: char netobuf[BUFSIZ], *nfrontp = netobuf, *nbackp = netobuf; ! 27: ! 28: char hisopts[256]; ! 29: char myopts[256]; ! 30: ! 31: char doopt[] = { IAC, DO, '%', 'c', 0 }; ! 32: char dont[] = { IAC, DONT, '%', 'c', 0 }; ! 33: char will[] = { IAC, WILL, '%', 'c', 0 }; ! 34: char wont[] = { IAC, WONT, '%', 'c', 0 }; ! 35: ! 36: int connected; ! 37: int net; ! 38: int showoptions = 0; ! 39: int options; ! 40: int debug = 0; ! 41: int crmod = 0; ! 42: char *prompt; ! 43: char escape = 035; ! 44: ! 45: char line[200]; ! 46: int margc; ! 47: char *margv[20]; ! 48: ! 49: jmp_buf toplevel; ! 50: ! 51: extern int errno; ! 52: ! 53: int tn(), quit(), bye(), help(); ! 54: int setescape(), status(), toggle(), setoptions(); ! 55: int setcrmod(), setdebug(); ! 56: ! 57: #define HELPINDENT (sizeof ("connect")) ! 58: ! 59: struct cmd { ! 60: char *name; /* command name */ ! 61: char *help; /* help string */ ! 62: int (*handler)(); /* routine which executes command */ ! 63: }; ! 64: ! 65: char openhelp[] = "open connection to a site"; ! 66: char closehelp[] = "close current connection"; ! 67: char quithelp[] = "exit telnet"; ! 68: char debughelp[] = "toggle debugging"; ! 69: char escapehelp[] = "set escape character"; ! 70: char statushelp[] = "print status information"; ! 71: char helphelp[] = "print help information"; ! 72: char optionshelp[] = "toggle viewing of options processing"; ! 73: char crmodhelp[] = "toggle mapping of received carriage returns"; ! 74: ! 75: struct cmd cmdtab[] = { ! 76: { "open", openhelp, tn }, ! 77: { "close", closehelp, bye }, ! 78: { "quit", quithelp, quit }, ! 79: { "escape", escapehelp, setescape }, ! 80: { "status", statushelp, status }, ! 81: { "options", optionshelp, setoptions }, ! 82: { "crmod", crmodhelp, setcrmod }, ! 83: { "debug", debughelp, setdebug }, ! 84: { "?", helphelp, help }, ! 85: 0 ! 86: }; ! 87: ! 88: int intr(), deadpeer(); ! 89: char *control(); ! 90: struct cmd *getcmd(); ! 91: ! 92: struct tchars otc; ! 93: struct ltchars oltc; ! 94: struct sgttyb ottyb; ! 95: ! 96: char *fgets(); ! 97: ! 98: main(argc, argv) ! 99: int argc; ! 100: char *argv[]; ! 101: { ! 102: ioctl(0, TIOCGETP, (char *)&ottyb); ! 103: ioctl(0, TIOCGETC, (char *)&otc); ! 104: ioctl(0, TIOCGLTC, (char *)&oltc); ! 105: setbuf(stdin, (char *)0); ! 106: setbuf(stdout, (char *)0); ! 107: prompt = argv[0]; ! 108: if (argc > 1 && !strcmp(argv[1], "-d")) ! 109: debug++, argv++, argc--; ! 110: if (argc > 1 && !strcmp(argv[1], "-e")) ! 111: setescape(argc-1, &argv[1]), argv+=2, argc-=2; ! 112: if (argc != 1) { ! 113: if (setjmp(toplevel) != 0) ! 114: exit(0); ! 115: tn(argc, argv); ! 116: } ! 117: setjmp(toplevel); ! 118: for (;;) ! 119: command(1); ! 120: } ! 121: ! 122: char *hostname; ! 123: ! 124: tn(argc, argv) ! 125: int argc; ! 126: char *argv[]; ! 127: { ! 128: register int c; ! 129: char *serv; ! 130: char *tcptofs(); ! 131: ! 132: if (connected) { ! 133: printf("?Already connected to %s\n", hostname); ! 134: return; ! 135: } ! 136: if (argc < 2) { ! 137: strcpy(line, "Connect "); ! 138: printf("(to) "); ! 139: fgets(&line[strlen(line)], sizeof(line)-strlen(line)-1, stdin); ! 140: makeargv(); ! 141: argc = margc; ! 142: argv = margv; ! 143: } ! 144: if (argc > 3) { ! 145: printf("usage: %s host-name [port]\n", argv[0]); ! 146: return; ! 147: } ! 148: hostname = argv[1]; ! 149: if (argc == 3) ! 150: serv = tcptofs(atoi(argv[2])); ! 151: else ! 152: serv = tcptofs(23); ! 153: signal(SIGINT, intr); ! 154: signal(SIGPIPE, SIG_IGN); ! 155: signal(SIGHUP, SIG_IGN); ! 156: printf("Trying...\n"); ! 157: net = ipcopen(ipcpath(hostname, "tcp", serv), "light hup"); ! 158: if(net < 0){ ! 159: perror("telnet: connect"); ! 160: signal(SIGINT, SIG_DFL); ! 161: return; ! 162: } ! 163: connected++; ! 164: call(status, (int)"status", 0); ! 165: telnet(net); ! 166: fprintf(stderr, "Connection closed by foreign host.\n"); ! 167: exit(1); ! 168: } ! 169: ! 170: /* ! 171: * Print status about the connection. ! 172: */ ! 173: /*VARARGS*/ ! 174: status() ! 175: { ! 176: if (connected) ! 177: printf("Connected to %s.\n", hostname); ! 178: else ! 179: printf("No connection.\n"); ! 180: printf("Escape character is '%s'.\n", control(escape)); ! 181: fflush(stdout); ! 182: } ! 183: ! 184: makeargv() ! 185: { ! 186: register char *cp; ! 187: register char **argp = margv; ! 188: ! 189: margc = 0; ! 190: for (cp = line; *cp;) { ! 191: while (isspace(*cp)) ! 192: cp++; ! 193: if (*cp == '\0') ! 194: break; ! 195: *argp++ = cp; ! 196: margc += 1; ! 197: while (*cp != '\0' && !isspace(*cp)) ! 198: cp++; ! 199: if (*cp == '\0') ! 200: break; ! 201: *cp++ = '\0'; ! 202: } ! 203: *argp++ = 0; ! 204: } ! 205: ! 206: /*VARARGS*/ ! 207: bye() ! 208: { ! 209: register char *op; ! 210: ! 211: (void) mode(0); ! 212: if (connected) { ! 213: printf("Connection closed.\n"); ! 214: close(net); ! 215: connected = 0; ! 216: /* reset his options */ ! 217: for (op = hisopts; op < &hisopts[256]; op++) ! 218: *op = 0; ! 219: } ! 220: } ! 221: ! 222: /*VARARGS*/ ! 223: quit() ! 224: { ! 225: call(bye, (int)"bye", 0); ! 226: exit(0); ! 227: } ! 228: ! 229: /* ! 230: * Help command. ! 231: */ ! 232: help(argc, argv) ! 233: int argc; ! 234: char *argv[]; ! 235: { ! 236: register struct cmd *c; ! 237: ! 238: if (argc == 1) { ! 239: printf("Commands may be abbreviated. Commands are:\n\n"); ! 240: for (c = cmdtab; c->name; c++) ! 241: printf("%-*s\t%s\n", HELPINDENT, c->name, c->help); ! 242: return; ! 243: } ! 244: while (--argc > 0) { ! 245: register char *arg; ! 246: arg = *++argv; ! 247: c = getcmd(arg); ! 248: if (c == (struct cmd *)-1) ! 249: printf("?Ambiguous help command %s\n", arg); ! 250: else if (c == (struct cmd *)0) ! 251: printf("?Invalid help command %s\n", arg); ! 252: else ! 253: printf("%s\n", c->help); ! 254: } ! 255: } ! 256: ! 257: /* ! 258: * Call routine with argc, argv set from args (terminated by 0). ! 259: * VARARGS2 ! 260: */ ! 261: call(routine, args) ! 262: int (*routine)(); ! 263: int args; ! 264: { ! 265: register int *argp; ! 266: register int argc; ! 267: ! 268: for (argc = 0, argp = &args; *argp++ != 0; argc++) ! 269: ; ! 270: (*routine)(argc, &args); ! 271: } ! 272: ! 273: struct tchars notc = { -1, -1, -1, -1, -1, -1 }; ! 274: struct ltchars noltc = { -1, -1, -1, -1, -1, -1 }; ! 275: ! 276: mode(f) ! 277: register int f; ! 278: { ! 279: static int prevmode = 0; ! 280: struct tchars *tc; ! 281: struct ltchars *ltc; ! 282: struct sgttyb sb; ! 283: int onoff, old; ! 284: ! 285: if (prevmode == f) ! 286: return (f); ! 287: old = prevmode; ! 288: prevmode = f; ! 289: sb = ottyb; ! 290: switch (f) { ! 291: ! 292: case 0: ! 293: onoff = 0; ! 294: tc = &otc; ! 295: ltc = &oltc; ! 296: break; ! 297: ! 298: case 1: ! 299: case 2: ! 300: sb.sg_flags |= CBREAK; ! 301: if (f == 1) ! 302: sb.sg_flags &= ~(ECHO|CRMOD); ! 303: else ! 304: sb.sg_flags |= ECHO|CRMOD; ! 305: sb.sg_erase = sb.sg_kill = -1; ! 306: tc = ¬c; ! 307: ltc = &noltc; ! 308: onoff = 1; ! 309: break; ! 310: ! 311: default: ! 312: return; ! 313: } ! 314: ioctl(fileno(stdin), TIOCSLTC, (char *)ltc); ! 315: ioctl(fileno(stdin), TIOCSETC, (char *)tc); ! 316: ioctl(fileno(stdin), TIOCSETP, (char *)&sb); ! 317: return (old); ! 318: } ! 319: ! 320: char sibuf[BUFSIZ], *sbp; ! 321: char tibuf[BUFSIZ], *tbp; ! 322: int scc, tcc; ! 323: int hungup; ! 324: ! 325: catchhup() ! 326: { ! 327: hungup = 1; ! 328: signal(SIGHUP, SIG_IGN); ! 329: } ! 330: ! 331: /* ! 332: * Select from tty and network... ! 333: */ ! 334: telnet(s) ! 335: int s; ! 336: { ! 337: register int c; ! 338: int tin = fileno(stdin), tout = fileno(stdout); ! 339: int on = 1; ! 340: ! 341: hungup = 0; ! 342: (void) mode(2); ! 343: signal(SIGHUP, catchhup); ! 344: while(!hungup) { ! 345: fd_set ibits, obits; ! 346: int nfds; ! 347: ! 348: FD_ZERO(ibits); ! 349: FD_ZERO(obits); ! 350: if (nfrontp - nbackp) ! 351: FD_SET(s, obits); ! 352: else ! 353: FD_SET(tin, ibits); ! 354: if (tfrontp - tbackp) ! 355: FD_SET(tout, obits); ! 356: else ! 357: FD_SET(s, ibits); ! 358: if (scc < 0 && tcc < 0) ! 359: break; ! 360: nfds = select(NOFILE, &ibits, &obits, 1000); ! 361: if (nfds==0) ! 362: continue; ! 363: else if (nfds<0) ! 364: break; ! 365: ! 366: /* ! 367: * Something to read from the network... ! 368: */ ! 369: if (FD_ISSET(s, ibits)) { ! 370: scc = read(s, sibuf, sizeof (sibuf)); ! 371: if (scc <= 0) ! 372: break; ! 373: sbp = sibuf; ! 374: } ! 375: ! 376: /* ! 377: * Something to read from the tty... ! 378: */ ! 379: if (FD_ISSET(tin, ibits)) { ! 380: tcc = read(tin, tibuf, sizeof (tibuf)); ! 381: if (tcc < 0) ! 382: break; ! 383: tbp = tibuf; ! 384: } ! 385: ! 386: while (tcc > 0) { ! 387: register int c; ! 388: ! 389: if ((&netobuf[BUFSIZ] - nfrontp) < 2) ! 390: break; ! 391: c = *tbp++ & 0377, tcc--; ! 392: if (strip(c) == escape) { ! 393: command(0); ! 394: tcc = 0; ! 395: break; ! 396: } ! 397: if (c == IAC) ! 398: *nfrontp++ = c; ! 399: *nfrontp++ = c; ! 400: } ! 401: if (FD_ISSET(s, obits) && (nfrontp - nbackp) > 0) ! 402: netflush(s); ! 403: if (scc > 0) ! 404: telrcv(); ! 405: if (FD_ISSET(tout, obits) && (tfrontp - tbackp) > 0) ! 406: ttyflush(tout); ! 407: } ! 408: signal(SIGHUP, SIG_IGN); ! 409: (void) mode(0); ! 410: } ! 411: ! 412: command(top) ! 413: int top; ! 414: { ! 415: register struct cmd *c; ! 416: int oldmode, wasopen; ! 417: ! 418: oldmode = mode(0); ! 419: if (!top) ! 420: putchar('\n'); ! 421: else ! 422: signal(SIGINT, SIG_DFL); ! 423: for (;;) { ! 424: printf("%s> ", prompt); ! 425: if (fgets(line, sizeof(line), stdin) == 0) { ! 426: if (feof(stdin)) { ! 427: clearerr(stdin); ! 428: putchar('\n'); ! 429: } ! 430: break; ! 431: } ! 432: if (line[0] == 0) ! 433: break; ! 434: makeargv(); ! 435: c = getcmd(margv[0]); ! 436: if (c == (struct cmd *)-1) { ! 437: printf("?Ambiguous command\n"); ! 438: continue; ! 439: } ! 440: if (c == 0) { ! 441: printf("?Invalid command\n"); ! 442: continue; ! 443: } ! 444: (*c->handler)(margc, margv); ! 445: if (c->handler != help) ! 446: break; ! 447: } ! 448: if (!top) { ! 449: if (!connected) ! 450: longjmp(toplevel, 1); ! 451: (void) mode(oldmode); ! 452: } ! 453: } ! 454: ! 455: /* ! 456: * Telnet receiver states for fsm ! 457: */ ! 458: #define TS_DATA 0 ! 459: #define TS_IAC 1 ! 460: #define TS_WILL 2 ! 461: #define TS_WONT 3 ! 462: #define TS_DO 4 ! 463: #define TS_DONT 5 ! 464: ! 465: telrcv() ! 466: { ! 467: register int c; ! 468: static int state = TS_DATA; ! 469: ! 470: while (scc > 0) { ! 471: c = *sbp++ & 0377, scc--; ! 472: switch (state) { ! 473: ! 474: case TS_DATA: ! 475: if (c == IAC) { ! 476: state = TS_IAC; ! 477: continue; ! 478: } ! 479: *tfrontp++ = c; ! 480: /* ! 481: * This hack is needed since we can't set ! 482: * CRMOD on output only. Machines like MULTICS ! 483: * like to send \r without \n; since we must ! 484: * turn off CRMOD to get proper input, the mapping ! 485: * is done here (sigh). ! 486: */ ! 487: if (c == '\r' && crmod) ! 488: *tfrontp++ = '\n'; ! 489: continue; ! 490: ! 491: case TS_IAC: ! 492: switch (c) { ! 493: ! 494: case WILL: ! 495: state = TS_WILL; ! 496: continue; ! 497: ! 498: case WONT: ! 499: state = TS_WONT; ! 500: continue; ! 501: ! 502: case DO: ! 503: state = TS_DO; ! 504: continue; ! 505: ! 506: case DONT: ! 507: state = TS_DONT; ! 508: continue; ! 509: ! 510: case DM: ! 511: ioctl(fileno(stdout), TIOCFLUSH, 0); ! 512: break; ! 513: ! 514: case NOP: ! 515: case GA: ! 516: break; ! 517: ! 518: default: ! 519: break; ! 520: } ! 521: state = TS_DATA; ! 522: continue; ! 523: ! 524: case TS_WILL: ! 525: printoption("RCVD", will, c, !hisopts[c]); ! 526: if (!hisopts[c]) ! 527: willoption(c); ! 528: state = TS_DATA; ! 529: continue; ! 530: ! 531: case TS_WONT: ! 532: printoption("RCVD", wont, c, hisopts[c]); ! 533: if (hisopts[c]) ! 534: wontoption(c); ! 535: state = TS_DATA; ! 536: continue; ! 537: ! 538: case TS_DO: ! 539: printoption("RCVD", doopt, c, !myopts[c]); ! 540: if (!myopts[c]) ! 541: dooption(c); ! 542: state = TS_DATA; ! 543: continue; ! 544: ! 545: case TS_DONT: ! 546: printoption("RCVD", dont, c, myopts[c]); ! 547: if (myopts[c]) { ! 548: myopts[c] = 0; ! 549: sprintf(nfrontp, wont, c); ! 550: nfrontp += sizeof (wont) - 2; ! 551: printoption("SENT", wont, c, 0); ! 552: } ! 553: state = TS_DATA; ! 554: continue; ! 555: } ! 556: } ! 557: } ! 558: ! 559: willoption(option) ! 560: int option; ! 561: { ! 562: char *fmt; ! 563: ! 564: switch (option) { ! 565: ! 566: case TELOPT_ECHO: ! 567: (void) mode(1); ! 568: ! 569: case TELOPT_SGA: ! 570: hisopts[option] = 1; ! 571: fmt = doopt; ! 572: break; ! 573: ! 574: case TELOPT_TM: ! 575: fmt = dont; ! 576: break; ! 577: ! 578: default: ! 579: fmt = dont; ! 580: break; ! 581: } ! 582: sprintf(nfrontp, fmt, option); ! 583: nfrontp += sizeof (dont) - 2; ! 584: printoption("SENT", fmt, option, 0); ! 585: } ! 586: ! 587: wontoption(option) ! 588: int option; ! 589: { ! 590: char *fmt; ! 591: ! 592: switch (option) { ! 593: ! 594: case TELOPT_ECHO: ! 595: (void) mode(2); ! 596: ! 597: case TELOPT_SGA: ! 598: hisopts[option] = 0; ! 599: fmt = dont; ! 600: break; ! 601: ! 602: default: ! 603: fmt = dont; ! 604: } ! 605: sprintf(nfrontp, fmt, option); ! 606: nfrontp += sizeof (doopt) - 2; ! 607: printoption("SENT", fmt, option, 0); ! 608: } ! 609: ! 610: dooption(option) ! 611: int option; ! 612: { ! 613: char *fmt; ! 614: ! 615: switch (option) { ! 616: ! 617: case TELOPT_TM: ! 618: fmt = wont; ! 619: break; ! 620: ! 621: case TELOPT_ECHO: ! 622: (void) mode(2); ! 623: fmt = will; ! 624: hisopts[option] = 0; ! 625: break; ! 626: ! 627: case TELOPT_SGA: ! 628: fmt = will; ! 629: break; ! 630: ! 631: default: ! 632: fmt = wont; ! 633: break; ! 634: } ! 635: sprintf(nfrontp, fmt, option); ! 636: nfrontp += sizeof (doopt) - 2; ! 637: printoption("SENT", fmt, option, 0); ! 638: } ! 639: ! 640: /* ! 641: * Set the escape character. ! 642: */ ! 643: setescape(argc, argv) ! 644: int argc; ! 645: char *argv[]; ! 646: { ! 647: register char *arg; ! 648: char buf[50]; ! 649: ! 650: if (argc > 2) ! 651: arg = argv[1]; ! 652: else { ! 653: printf("new escape character: "); ! 654: fgets(buf, sizeof(buf), stdin); ! 655: arg = buf; ! 656: } ! 657: if (arg[0] == '\0') ! 658: escape = 0200; ! 659: else { ! 660: escape = arg[0]; ! 661: printf("Escape character is '%s'.\n", control(escape)); ! 662: } ! 663: fflush(stdout); ! 664: } ! 665: ! 666: /*VARARGS*/ ! 667: setoptions() ! 668: { ! 669: ! 670: showoptions = !showoptions; ! 671: printf("%s show option processing.\n", showoptions ? "Will" : "Wont"); ! 672: fflush(stdout); ! 673: } ! 674: ! 675: /*VARARGS*/ ! 676: setcrmod() ! 677: { ! 678: ! 679: crmod = !crmod; ! 680: printf("%s map carriage return on output.\n", crmod ? "Will" : "Wont"); ! 681: fflush(stdout); ! 682: } ! 683: ! 684: /*VARARGS*/ ! 685: setdebug() ! 686: { ! 687: ! 688: debug = !debug; ! 689: printf("%s turn on socket level debugging.\n", ! 690: debug ? "Will" : "Wont"); ! 691: fflush(stdout); ! 692: } ! 693: ! 694: /* ! 695: * Construct a control character sequence ! 696: * for a special character. ! 697: */ ! 698: char * ! 699: control(c) ! 700: register int c; ! 701: { ! 702: static char buf[3]; ! 703: ! 704: if ((c&0377) == 0200) ! 705: return (""); ! 706: if (c == 0177) ! 707: return ("^?"); ! 708: if (c >= 040) { ! 709: buf[0] = c; ! 710: buf[1] = 0; ! 711: } else { ! 712: buf[0] = '^'; ! 713: buf[1] = '@'+c; ! 714: buf[2] = 0; ! 715: } ! 716: return (buf); ! 717: } ! 718: ! 719: struct cmd * ! 720: getcmd(name) ! 721: register char *name; ! 722: { ! 723: register char *p, *q; ! 724: register struct cmd *c, *found; ! 725: register int nmatches, longest; ! 726: ! 727: longest = 0; ! 728: nmatches = 0; ! 729: found = 0; ! 730: for (c = cmdtab; p = c->name; c++) { ! 731: for (q = name; *q == *p++; q++) ! 732: if (*q == 0) /* exact match? */ ! 733: return (c); ! 734: if (!*q) { /* the name was a prefix */ ! 735: if (q - name > longest) { ! 736: longest = q - name; ! 737: nmatches = 1; ! 738: found = c; ! 739: } else if (q - name == longest) ! 740: nmatches++; ! 741: } ! 742: } ! 743: if (nmatches > 1) ! 744: return ((struct cmd *)-1); ! 745: return (found); ! 746: } ! 747: ! 748: intr() ! 749: { ! 750: (void) mode(0); ! 751: longjmp(toplevel, -1); ! 752: } ! 753: ! 754: ttyflush(fd) ! 755: { ! 756: int n; ! 757: ! 758: if ((n = tfrontp - tbackp) > 0) ! 759: n = write(fd, tbackp, n); ! 760: if (n < 0) ! 761: return; ! 762: tbackp += n; ! 763: if (tbackp == tfrontp) ! 764: tbackp = tfrontp = ttyobuf; ! 765: } ! 766: ! 767: netflush(fd) ! 768: { ! 769: int n; ! 770: ! 771: if ((n = nfrontp - nbackp) > 0) ! 772: n = write(fd, nbackp, n); ! 773: if (n > 0) ! 774: nbackp += n; ! 775: if (nbackp == nfrontp) ! 776: nbackp = nfrontp = netobuf; ! 777: } ! 778: ! 779: /*VARARGS*/ ! 780: printoption(direction, fmt, option, what) ! 781: char *direction, *fmt; ! 782: int option, what; ! 783: { ! 784: if (!showoptions) ! 785: return; ! 786: printf("%s ", direction); ! 787: if (fmt == doopt) ! 788: fmt = "do"; ! 789: else if (fmt == dont) ! 790: fmt = "dont"; ! 791: else if (fmt == will) ! 792: fmt = "will"; ! 793: else if (fmt == wont) ! 794: fmt = "wont"; ! 795: else ! 796: fmt = "???"; ! 797: if (option < TELOPT_SUPDUP) ! 798: printf("%s %s", fmt, telopts[option]); ! 799: else ! 800: printf("%s %d", fmt, option); ! 801: if (*direction == '<') { ! 802: printf("\r\n"); ! 803: return; ! 804: } ! 805: printf(" (%s)\r\n", what ? "reply" : "don't reply"); ! 806: }
This archive runs on limited infrastructure. Preserving old code on modern bandwidth. Automated agents are requested to crawl responsibly.