Annotation of 43BSD/etc/named/ns_resp.c, revision 1.1

1.1     ! root        1: #ifndef lint
        !             2: static char sccsid[] = "@(#)ns_resp.c  4.3 (Berkeley) 5/30/86";
        !             3: #endif
        !             4: 
        !             5: /*
        !             6:  * Copyright (c) 1986 Regents of the University of California
        !             7:  *     All Rights Reserved
        !             8:  */
        !             9: 
        !            10: #include <stdio.h>
        !            11: #include <sys/types.h>
        !            12: #include <sys/time.h>
        !            13: #include <sys/socket.h>
        !            14: #include <netinet/in.h>
        !            15: #include <syslog.h>
        !            16: #include <arpa/nameser.h>
        !            17: #include "ns.h"
        !            18: #include "db.h"
        !            19: 
        !            20: extern int errno;
        !            21: extern char *dnptrs[];
        !            22: extern char *malloc();
        !            23: struct databuf *nsp[MAXNS], **nspp;
        !            24: 
        !            25: /*
        !            26:  * Handle a response from a forwarded query.
        !            27:  */
        !            28: ns_resp(msg, msglen)
        !            29:        char *msg;
        !            30:        int msglen;
        !            31: {
        !            32:        register struct qinfo *qp;
        !            33:        register HEADER *hp;
        !            34:        register char *cp, *tp;
        !            35:        int i, c, n, ancount, nscount, arcount;
        !            36:        int type;
        !            37:        int qtype, qclass;
        !            38:        int cname = 0; /* flag for processing cname response */
        !            39:        int count, founddata;
        !            40:        int buflen;
        !            41:        int newmsglen;
        !            42:        char name[MAXDNAME], *dname;
        !            43:        char *fname;
        !            44:        char *newmsg;
        !            45: 
        !            46:        struct hashbuf *htp;
        !            47:        struct databuf *dp, *tmp, *pdp;
        !            48:        struct namebuf *np;
        !            49: 
        !            50: #ifdef DEBUG
        !            51:        if (debug >= 3)
        !            52:                fprintf(ddt,"ns_resp(%x, %d)\n", msg, msglen);
        !            53: #endif
        !            54:        hp = (HEADER *) msg;
        !            55:        if( (qp = qfindid(hp->id)) == NULL )
        !            56:                return;
        !            57: 
        !            58:        if (hp->rcode != NOERROR || hp->opcode != QUERY)
        !            59:                goto retmsg;
        !            60:        /*
        !            61:         * Skip query section
        !            62:         */
        !            63:        cp = msg + sizeof(HEADER);
        !            64:        if (hp->qdcount) {
        !            65:                if ((n = dn_skip(cp)) < 0) {
        !            66: #ifdef DEBUG
        !            67:                        if (debug)
        !            68:                            fprintf(ddt,"FORMERR ns_resp() dn_skip failed\n");
        !            69: #endif
        !            70:                        hp->rcode = FORMERR;
        !            71:                        goto retmsg;
        !            72:                }
        !            73:                cp += n;
        !            74:                qtype = getshort(cp);
        !            75:                cp += sizeof(u_short);
        !            76:                qclass = getshort(cp);
        !            77:                cp += sizeof(u_short);
        !            78:        }
        !            79:        /*
        !            80:         * Save answers, name server, and additional records for future use.
        !            81:         */
        !            82:        ancount = ntohs(hp->ancount);
        !            83:        nscount = ntohs(hp->nscount);
        !            84:        arcount = ntohs(hp->arcount);
        !            85:        if (ancount == 1 || nscount) {
        !            86:                /*
        !            87:                 *  Check if it's a CNAME responce
        !            88:                 */
        !            89:                tp = cp;
        !            90:                tp += dn_skip(tp); /* name */
        !            91:                type = getshort(tp);
        !            92:                tp += sizeof(u_short); /* type */
        !            93:                if (type == T_CNAME) {
        !            94:                        tp += sizeof(u_short); /* class */
        !            95:                        tp += sizeof(u_long);  /* ttl */
        !            96:                        tp += sizeof(u_short); /* dlen */
        !            97:                        cname++;
        !            98: #ifdef DEBUG
        !            99:                        if (debug) {
        !           100:                                fprintf(ddt,"CNAME - needs more processing\n");
        !           101:                        }
        !           102: #endif
        !           103:                        if (!qp->q_cmsglen) {
        !           104:                                qp->q_cmsg = qp->q_msg;
        !           105:                                qp->q_cmsglen = qp->q_msglen;
        !           106:                        }
        !           107:                }
        !           108:        }
        !           109:        /*
        !           110:         * Add the info recived in the responce to the Data Base
        !           111:         */
        !           112:        c = ancount + nscount + arcount;
        !           113:        nspp = nsp;
        !           114:        for (i = 0; i < c; i++) {
        !           115:                if (cp >= msg + msglen) {
        !           116: #ifdef DEBUG
        !           117:                        if (debug)
        !           118:                            fprintf(ddt, "FORMERR ns_resp() message bad count?\n");
        !           119: #endif
        !           120:                        hp->rcode = FORMERR;
        !           121:                        goto retmsg;
        !           122:                }
        !           123:                if ((n = doupdate(msg, msglen, cp, 0,
        !           124:                    !ancount && i < nscount)) < 0) {
        !           125: #ifdef DEBUG
        !           126:                        if (debug)
        !           127:                            fprintf(ddt,"FORMERR ns_resp() doupdate failed\n");
        !           128: #endif
        !           129:                        hp->rcode = FORMERR;
        !           130:                        goto retmsg;
        !           131:                }
        !           132:                cp += n;
        !           133:        }
        !           134:        if (cp > msg + msglen) {
        !           135: #ifdef DEBUG
        !           136:                if (debug)
        !           137:                    fprintf(ddt,"FORMERR ns_resp()  packet size err %d, %d\n",
        !           138:                       cp-msg, msglen);
        !           139: #endif
        !           140:                hp->rcode = FORMERR;
        !           141:                goto retmsg;
        !           142:        }
        !           143:        if ((qtype == C_ANY) && ancount)
        !           144:                goto retmsg;
        !           145:        if ((!cname && !qp->q_cmsglen) && (ancount || nscount == 0))
        !           146:                goto retmsg;
        !           147: 
        !           148:        /*
        !           149:         * All messages in here need further processing.  i.e. they
        !           150:         * are either CNAMEs or we got referred again.
        !           151:         */
        !           152:        count = 0;
        !           153:        founddata = 0;
        !           154:        dname = name;
        !           155:        if ((newmsg = malloc(BUFSIZ)) == NULL) {
        !           156: #if DEBUG
        !           157:                if (debug)
        !           158:                        fprintf(ddt,"ns_resp: malloc error\n");
        !           159: #endif
        !           160:                syslog(LOG_ERR, "ns_resp: Out Of Memory");
        !           161:                hp->rcode = SERVFAIL;
        !           162:                goto retmsg;
        !           163:        }
        !           164:        buflen = BUFSIZ;
        !           165:        if ((!cname && qp->q_cmsglen) && ancount) {
        !           166: #if DEBUG
        !           167:                if (debug) {
        !           168:                        fprintf(ddt,"Cname second pass\n");
        !           169:                }
        !           170: #endif
        !           171:                newmsglen = qp->q_cmsglen;
        !           172:                bcopy(qp->q_cmsg, newmsg, newmsglen);
        !           173:        } else {
        !           174:                newmsglen = msglen;
        !           175:                bcopy(msg, newmsg, newmsglen);
        !           176:        }
        !           177:        buflen = buflen - newmsglen;
        !           178:        hp = (HEADER *) newmsg;
        !           179:        dnptrs[0] = newmsg;
        !           180:        dnptrs[1] = NULL;
        !           181:        cp = newmsg + sizeof(HEADER);
        !           182:        if (cname)
        !           183:                cp += dn_skip(cp) + QFIXEDSZ;
        !           184:        if ((n = dn_expand(newmsg, newmsg + newmsglen,
        !           185:                cp, dname, sizeof(name))) < 0) {
        !           186: #ifdef DEBUG
        !           187:                if (debug)
        !           188:                        fprintf(ddt,"dn_expand failed\n" );
        !           189: #endif
        !           190:                hp->rcode = SERVFAIL;
        !           191:                free(newmsg);
        !           192:                goto retmsg;
        !           193:        }
        !           194:        cp = newmsg + sizeof(HEADER);
        !           195:        if (cname)
        !           196:                cp += dn_skip(cp);
        !           197:        else
        !           198:                cp += n;
        !           199:        cp += QFIXEDSZ;
        !           200: 
        !           201: again:
        !           202:        htp = hashtab;          /* lookup relative to root */
        !           203: #ifdef DEBUG
        !           204:        if (debug)
        !           205:                fprintf(ddt,"ns_resp() nlookup(%s)\n",dname);
        !           206: #endif
        !           207:        np = nlookup(dname, &htp, &fname, 0);
        !           208:        if (np == (struct namebuf *)NULL)
        !           209:                fname = "";
        !           210:        if (fname != dname)
        !           211:                goto findns;
        !           212: #ifdef DEBUG
        !           213:        if (debug)
        !           214:                fprintf(ddt,"found '%s'\n", dname);
        !           215: #endif
        !           216:        pdp = NULL;
        !           217:        dp = np->n_data;
        !           218:        /* look for the data */
        !           219:        while (dp != NULL) {
        !           220:                if (!wanted(dp, qclass, qtype)) {
        !           221:                        pdp = dp;
        !           222:                        dp = dp->d_next;
        !           223:                        continue;
        !           224:                }
        !           225:                if ((n = make_rr(dname, dp, cp, buflen, 1)) < 0) {
        !           226:                        if (n == -1) {
        !           227:                                hp->tc = 1;
        !           228:                                break;
        !           229:                        }
        !           230:                        /* delete old cache entry */
        !           231: #ifdef DEBUG
        !           232:                        if (debug >= 5)
        !           233:                                fprintf(ddt,"deleting cache entry\n");
        !           234: #endif
        !           235:                        rminv(dp);
        !           236:                        tmp = dp->d_next;
        !           237:                        free((char *)dp);
        !           238:                        dp = tmp;
        !           239:                        if (pdp == NULL)
        !           240:                                np->n_data = dp;
        !           241:                        else
        !           242:                                pdp->d_next = dp;
        !           243:                        continue;
        !           244:                }
        !           245:                cp += n;
        !           246:                buflen -= n;
        !           247:                count++;
        !           248:                if (dp->d_zone)
        !           249:                        hp->aa = 1;
        !           250:                if (dp->d_type == T_CNAME) {
        !           251:                        if (type == T_ANY)
        !           252:                                break;
        !           253:                        dname = dp->d_data;
        !           254:                        goto again;
        !           255:                }
        !           256:                founddata++;
        !           257:                pdp = dp;
        !           258:                dp = dp->d_next;
        !           259:        }
        !           260: #ifdef DEBUG
        !           261:        if (debug >= 5)
        !           262:                fprintf(ddt,"count = %d, founddata = %d\n", count, founddata);
        !           263: #endif
        !           264:        if (count)
        !           265:                hp->ancount = htons((u_short)count);
        !           266: 
        !           267: findns:
        !           268: 
        !           269:        /*
        !           270:         * Look for name servers to refer to and fill in the authority
        !           271:         * section or record the address for forwarding the query
        !           272:         * (recursion desired).
        !           273:         */
        !           274:        for (count = 0;; np = np->n_parent) {
        !           275: #ifdef DEBUG
        !           276:                if (debug >= 5)
        !           277:                        fprintf(ddt, "fname = '%s'\n", fname);
        !           278: #endif
        !           279:                if (*fname == '\0') {
        !           280:                        for (np = hashtab->h_tab[0]; np != NULL;
        !           281:                             np = np->n_next)
        !           282:                                if (np->n_dname[0] == '\0')
        !           283:                                        goto foundns;
        !           284: #ifdef DEBUG
        !           285:                        if (debug)
        !           286:                                fprintf(ddt, "No root nameserver?\n");
        !           287: #endif
        !           288:                        syslog(LOG_ERR, "No root Nameserver\n");
        !           289:                        hp->rcode = SERVFAIL;
        !           290:                        break;
        !           291:                }
        !           292: foundns:
        !           293:                nspp = nsp;
        !           294:                pdp = NULL;
        !           295:                dp = np->n_data;
        !           296:                while (dp != NULL) {
        !           297:                        if (!match(dp, qclass, T_NS)) {
        !           298:                                pdp = dp;
        !           299:                                dp = dp->d_next;
        !           300:                                continue;
        !           301:                        }
        !           302:                        if (!founddata) {
        !           303:                                if (nspp < &nsp[MAXNS-1])
        !           304:                                        *nspp++ = dp;
        !           305:                                pdp = dp;
        !           306:                                dp = dp->d_next;
        !           307:                                continue;
        !           308:                        }
        !           309:                        if ((n = make_rr(fname, dp, cp, buflen, 1)) < 0) {
        !           310:                                if (n == -1) {
        !           311:                                        hp->tc = 1;
        !           312:                                        break;
        !           313:                                }
        !           314:                                /* delete old cache entry */
        !           315: #ifdef DEBUG
        !           316:                                if (debug)
        !           317:                                        fprintf(ddt,"deleting cache entry\n");
        !           318: #endif
        !           319:                                rminv(dp);
        !           320:                                tmp = dp->d_next;               
        !           321:                                free((char *)dp);
        !           322:                                dp = tmp;
        !           323:                                if (pdp == NULL)
        !           324:                                        np->n_data = dp;
        !           325:                                else
        !           326:                                        pdp->d_next = dp;
        !           327:                                continue;
        !           328:                        }
        !           329:                        cp += n;
        !           330:                        buflen -= n;
        !           331:                        count++;
        !           332:                        pdp = dp;
        !           333:                        dp = dp->d_next;
        !           334:                }
        !           335:                if ((*fname == '\0') || (count > 0))
        !           336:                        break;
        !           337:                if (nspp != nsp)
        !           338:                        break;
        !           339:                if ((fname = index(fname, '.')) == NULL)
        !           340:                        fname = "";
        !           341:                else
        !           342:                        fname++;
        !           343:        }
        !           344: 
        !           345:        if (count && founddata) {
        !           346:                hp->nscount = htons((u_short)count);
        !           347:                cp += doaddinfo(hp, cp, buflen);
        !           348:                buflen = cp - newmsg;
        !           349:                msg = newmsg;
        !           350:                msglen = buflen;
        !           351:                hp = (HEADER *) msg;
        !           352:                goto retmsg;
        !           353:        }
        !           354: 
        !           355:        *nspp = NULL;
        !           356:        if (cname) {
        !           357:                newmsglen = res_mkquery(QUERY, dname, C_ANY, T_A, (char *)NULL,
        !           358:                                        0, NULL, newmsg, BUFSIZ);
        !           359:                qp->q_msglen = newmsglen;
        !           360:                hp->id = qp->q_nsid;
        !           361:        } else {
        !           362:                hp->ancount = 0;
        !           363:                hp->nscount = 0;
        !           364:                hp->arcount = 0;
        !           365:                hp->qr = 0;
        !           366:        }
        !           367:        qp->q_naddr = 0;
        !           368:        qp->q_curaddr = 0;
        !           369:        n = nslookup(nsp, qp);
        !           370: 
        !           371: #ifdef DEBUG
        !           372:                if (debug > 7) {
        !           373:                        int kjd;
        !           374: 
        !           375:                        fprintf(ddt,"n = %d\n",n);
        !           376:                        for (kjd = 0; kjd < qp->q_naddr; kjd++ )
        !           377:                                fprintf(ddt,"list %d-> %s (%d)\n",  kjd,
        !           378:                                        inet_ntoa(qp->q_addr[kjd].sin_addr),
        !           379:                                        ntohs(qp->q_addr[kjd].sin_port));
        !           380:                }
        !           381: #endif DEBUG
        !           382:        qp->q_msg = newmsg;
        !           383:        if (qp->q_cname++ == MAXCNAMES) {
        !           384:                hp->id = qp->q_id;
        !           385:                hp->rd = 1;
        !           386:                hp->ra = 1;             
        !           387:                if (qp->q_stream != QSTREAM_NULL) {
        !           388:                        (void) writemsg(qp->q_stream->s_rfd, newmsg, newmsglen);
        !           389:                        qp->q_stream->s_time = tt.tv_sec;
        !           390:                        qp->q_stream->s_refcnt--;
        !           391:                } else {
        !           392:                        if (sendto(ds, newmsg, newmsglen, 0, &qp->q_from,
        !           393:                                sizeof(qp->q_from)) < 0) {
        !           394: #ifdef DEBUG
        !           395:                                if (debug)
        !           396:                                        fprintf(ddt,"sendto failed\n");
        !           397: #endif
        !           398:                        }
        !           399:                }
        !           400:                qremove(qp);
        !           401:                return;
        !           402:        }
        !           403: #ifdef DEBUG
        !           404:        if (debug)
        !           405:                fprintf(ddt,"q_cname = %d\n",qp->q_cname);
        !           406: #endif
        !           407:        unsched(qp);
        !           408:        schedretry(qp, (time_t)RETRYTIME);
        !           409: 
        !           410: #ifdef DEBUG
        !           411:        if (debug >= 3)
        !           412:                fp_query(qp->q_msg, ddt);
        !           413:        if (debug)
        !           414:                fprintf(ddt,"try -> %s (%d)\n",
        !           415:                        inet_ntoa(qp->q_addr[0].sin_addr),
        !           416:                        ntohs(qp->q_addr[0].sin_port));
        !           417: #endif
        !           418:        if (sendto(ds, qp->q_msg, qp->q_msglen, 0,
        !           419:                &qp->q_addr[0], sizeof(qp->q_addr[0])) < 0) {
        !           420: #ifdef DEBUG
        !           421:                if (debug)
        !           422:                        fprintf(ddt, "error returning msg errno=%d\n",errno);
        !           423: #endif
        !           424:        }
        !           425:        return;
        !           426: 
        !           427: retmsg:
        !           428:        /*
        !           429:         * Pass answer back to original requestor.
        !           430:         */
        !           431:        hp->id = qp->q_id;
        !           432:        hp->rd = 1;             /* restore Recursion Desired bit */
        !           433:        hp->ra = 1;             /* Recursion is Available */
        !           434: #ifdef DEBUG
        !           435:        if (debug)
        !           436:                fprintf(ddt,"respond -> %s (%d)\n",
        !           437:                        inet_ntoa(qp->q_from.sin_addr),
        !           438:                        ntohs(qp->q_from.sin_port));
        !           439:        if (debug >= 10)
        !           440:                fp_query(msg, ddt);
        !           441: #endif
        !           442:        if (qp->q_stream == QSTREAM_NULL) {
        !           443:                if (sendto(ds, msg, msglen, 0,
        !           444:                    &qp->q_from, sizeof(qp->q_from)) < 0) {
        !           445: #ifdef DEBUG
        !           446:                        if (debug)
        !           447:                                fprintf(ddt,"sendto failed\n");
        !           448: #endif
        !           449:                }
        !           450: 
        !           451:        } else {
        !           452:                (void) writemsg(qp->q_stream->s_rfd, msg, msglen);
        !           453:                qp->q_stream->s_time = tt.tv_sec;
        !           454:                qp->q_stream->s_refcnt--;
        !           455:        }
        !           456:        if (msg == newmsg)
        !           457:                (void) free(newmsg);
        !           458:        /*
        !           459:         * Remove from table
        !           460:         */
        !           461:        qremove(qp);
        !           462: }
        !           463: 
        !           464: /*
        !           465:  * Decode the resource record 'rrp' and update the database.
        !           466:  * If savens is true, record pointer for forwarding queries a second time.
        !           467:  */
        !           468: doupdate(msg, msglen, rrp, zone, savens)
        !           469:        char *msg ;
        !           470:        char *rrp;
        !           471:        int  msglen, zone, savens;
        !           472: {
        !           473:        register char *cp;
        !           474:        register int n;
        !           475:        int class, type, dlen, n1;
        !           476:        u_long ttl;
        !           477:        struct databuf *dp;
        !           478:        char dname[MAXDNAME];
        !           479:        char data[BUFSIZ], *cp1;
        !           480: 
        !           481: #ifdef DEBUG
        !           482:        if (debug >= 3)
        !           483:                fprintf(ddt,"doupdate(%d, %d)\n", zone, savens);
        !           484: #endif
        !           485: 
        !           486:        cp = rrp;
        !           487:        if ((n = dn_expand(msg, msg + msglen, cp, dname, sizeof(dname))) < 0)
        !           488:                return (-1);
        !           489:        cp += n;
        !           490:        type = getshort(cp);
        !           491:        cp += sizeof(u_short);
        !           492:        class = getshort(cp);
        !           493:        cp += sizeof(u_short);
        !           494:        ttl = getlong(cp);
        !           495:        cp += sizeof(u_long);
        !           496:        dlen = getshort(cp);
        !           497:        cp += sizeof(u_short);
        !           498:        if (zone == 0) {
        !           499:                if (ttl == 0)
        !           500:                        ttl = 5 * 60;
        !           501:                ttl += (u_long) tt.tv_sec;
        !           502:        }
        !           503:        /*
        !           504:         * Convert the resource record data into the internal
        !           505:         * database format.
        !           506:         */
        !           507:        switch (type) {
        !           508:        case T_A:
        !           509:        case T_HINFO:
        !           510:        case T_UINFO:
        !           511:        case T_UID:
        !           512:        case T_GID:
        !           513:                cp1 = cp;
        !           514:                n = dlen;
        !           515:                cp += n;
        !           516:                break;
        !           517: 
        !           518:        case T_CNAME:
        !           519:        case T_MB:
        !           520:        case T_MG:
        !           521:        case T_MR:
        !           522:        case T_NS:
        !           523:        case T_PTR:
        !           524:                if ((n = dn_expand(msg, msg + msglen, cp, data,
        !           525:                   sizeof(data))) < 0)
        !           526:                        return (-1);
        !           527:                cp += n;
        !           528:                cp1 = data;
        !           529:                n = strlen(data) + 1;
        !           530:                break;
        !           531: 
        !           532:        case T_MINFO:
        !           533:        case T_SOA:
        !           534:                if ((n = dn_expand(msg, msg + msglen, cp, data,
        !           535:                    sizeof(data))) < 0)
        !           536:                        return (-1);
        !           537:                cp += n;
        !           538:                cp1 = data + (n = strlen(data) + 1);
        !           539:                n1 = sizeof(data) - n;
        !           540:                if (type == T_SOA)
        !           541:                        n1 -= 5 * sizeof(u_long);
        !           542:                if ((n = dn_expand(msg, msg + msglen, cp, cp1, n1)) < 0)
        !           543:                        return (-1);
        !           544:                cp += n;
        !           545:                cp1 += strlen(cp1) + 1;
        !           546:                if (type == T_SOA) {
        !           547:                        bcopy(cp, cp1, n = 5 * sizeof(u_long));
        !           548:                        cp += n;
        !           549:                        cp1 += n;
        !           550:                }
        !           551:                n = cp1 - data;
        !           552:                cp1 = data;
        !           553:                break;
        !           554: 
        !           555:        case T_MX:
        !           556:                /* grab preference */
        !           557:                bcopy(cp,data,sizeof(u_short));
        !           558:                cp1 = data + sizeof(u_short);
        !           559:                cp += sizeof(u_short);
        !           560: 
        !           561:                /* get name */
        !           562:                if ((n = dn_expand(msg, msg + msglen, cp, cp1,
        !           563:                    sizeof(data)-sizeof(u_short))) < 0)
        !           564:                        return(-1);
        !           565:                cp += n;
        !           566: 
        !           567:                /* compute end of data */
        !           568:                cp1 += strlen(cp1) + 1;
        !           569:                /* compute size of data */
        !           570:                n = cp1 - data;
        !           571:                cp1 = data;
        !           572:                break;
        !           573: 
        !           574:        default:
        !           575: #ifdef DEBUG
        !           576:                if (debug >= 3)
        !           577:                        fprintf(ddt,"unknown type %d\n", type);
        !           578: #endif
        !           579:                return ((cp - rrp) + dlen);
        !           580:        }
        !           581:        dp = savedata(class, type, ttl, cp1, n);
        !           582:        dp->d_zone = zone;
        !           583:        if ((n = db_update(dname, dp, dp, DB_NODATA)) < 0) {
        !           584: #ifdef DEBUG
        !           585:                if (debug && (n != DATAEXISTS))
        !           586:                        fprintf(ddt,"update failed (%d)\n", n);
        !           587:                else if (debug >= 3)
        !           588:                        fprintf(ddt,"update failed (DATAEXISTS)\n");
        !           589: #endif
        !           590:                (void) free((char *)dp);
        !           591:        } else if (savens && type == T_NS && nspp < &nsp[MAXNS-1])
        !           592:                *nspp++ = dp;
        !           593:        return (cp - rrp);
        !           594: }

unix.superglobalmegacorp.com

This archive runs on limited infrastructure. Preserving old code on modern bandwidth. Automated agents are requested to crawl responsibly.