/* * Copyright (c) 1985, 1989 Regents of the University of California. * All rights reserved. * * Redistribution and use in source and binary forms are permitted * provided that: (1) source distributions retain this entire copyright * notice and comment, and (2) distributions including binaries display * the following acknowledgement: ``This product includes software * developed by the University of California, Berkeley and its contributors'' * in the documentation or other materials provided with the distribution * and in all advertising materials mentioning features or use of this * software. Neither the name of the University nor the names of its * contributors may be used to endorse or promote products derived * from this software without specific prior written permission. * THIS SOFTWARE IS PROVIDED ``AS IS'' AND WITHOUT ANY EXPRESS OR * IMPLIED WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. */ #ifndef lint static char Version[] = "@(#)list.c e07@nikhef.nl (Eric Wassenaar) 991529"; #endif #include "host.h" #include "glob.h" /* * Nameserver information. * Stores names and addresses of all servers that are to be queried * for a zone transfer of the desired zone. Normally these are the * authoritative primary and/or secondary nameservers for the zone. */ char nsname[MAXNSNAME][MAXDNAME+1]; /* nameserver host name */ struct in_addr ipaddr[MAXNSNAME][MAXIPADDR]; /* nameserver addresses */ int naddrs[MAXNSNAME]; /* count of addresses */ int nservers = 0; /* count of nameservers */ #ifdef notyet typedef struct srvr_data { char sd_nsname[MAXDNAME+1]; /* nameserver host name */ struct in_addr sd_ipaddr[MAXIPADDR]; /* nameserver addresses */ int sd_naddrs; /* count of addresses */ } srvr_data_t; srvr_data_t nsinfo[MAXNSNAME]; /* nameserver info */ #endif bool authserver; /* server is supposed to be authoritative */ bool lameserver; /* server could not provide SOA service */ /* * Host information. * Stores names and (single) addresses encountered during the zone listing * of all A records that belong to the zone. Non-authoritative glue records * that do not belong to the zone are not stored. Glue records that belong * to a delegated zone will be filtered out later during the host count scan. * The host names are allocated dynamically. * The list itself is also allocated dynamically, to avoid static limits, * and to keep the initial bss of the executable to a reasonable size. * Allocation is done in chunks, to reduce considerable malloc overhead. * Note that the list will not shrink during recursive processing. */ #ifdef obsolete char *hostname[MAXHOSTS]; /* host name of host in zone */ ipaddr_t hostaddr[MAXHOSTS]; /* first host address */ bool multaddr[MAXHOSTS]; /* set if this is a multiple address host */ #endif typedef struct host_data { char *hd_hostname; /* host name of host in zone */ ipaddr_t hd_hostaddr; /* first host address */ bool hd_multaddr; /* set if this is a multiple address host */ } host_data_t; host_data_t *hostlist = NULL; /* info on hosts in zone */ int hostcount = 0; /* count of hosts in zone */ int maxhosts = 0; /* number of allocated hostlist entries */ #define MAXHOSTINCR 4096 /* chunk size to increment hostlist */ #define hostname(i) hostlist[i].hd_hostname #define hostaddr(i) hostlist[i].hd_hostaddr #define multaddr(i) hostlist[i].hd_multaddr /* * Delegated zone information. * Stores the names of the delegated zones encountered during the zone * listing. The names and the list itself are allocated dynamically. */ char **zonename = NULL; /* names of delegated zones within zone */ int zonecount = 0; /* count of delegated zones within zone */ /* * Address information. * Stores the (single) addresses of hosts found in all zones traversed. * Used to search for duplicate hosts (same address but different name). * The list of addresses is allocated dynamically, and remains allocated. * This has now been implemented as a hashed list, using the low-order * address bits as the hash key. */ #ifdef obsolete ipaddr_t *addrlist = NULL; /* global list of addresses */ int addrcount = 0; /* count of global addresses */ #endif /* * SOA record information. */ soa_data_t soa; /* buffer to store soa data */ int soacount = 0; /* count of SOA records during listing */ /* * Nameserver preference. * As per BIND 4.9.* resource records may be returned after round-robin * reshuffling each time they are retrieved. For NS records, this may * lead to an unfavorable order for doing zone transfers. * We apply some heuristic to sort the NS records according to their * preference with respect to a given list of preferred server domains. */ int nsrank[MAXNSNAME]; /* nameserver ranking after sorting */ int nspref[MAXNSNAME]; /* nameserver preference value */ /* ** LIST_ZONE -- Basic routine to do complete zone listing and checking ** ------------------------------------------------------------------- ** ** Returns: ** TRUE if the requested info was processed successfully. ** FALSE otherwise. */ int total_calls = 0; /* number of calls for zone processing */ int total_check = 0; /* number of zones successfully processed */ int total_tries = 0; /* number of zone transfer attempts */ int total_zones = 0; /* number of successful zone transfers */ int total_hosts = 0; /* number of hosts in all traversed zones */ int total_dupls = 0; /* number of duplicates in all zones */ int zones_empty = 0; /* number of zones with zero hosts */ int zones_small = 0; /* number of zones with 1 - 9 hosts */ int zones_medium = 0; /* number of zones with 10 - 99 hosts */ int zones_large = 0; /* number of zones with 100 - 999 hosts */ int zones_huge = 0; /* number of zones with 1000 or more hosts */ int hosts_empty = 0; /* number of hosts within empty zones (:-) */ int hosts_small = 0; /* number of hosts within small zones */ int hosts_medium = 0; /* number of hosts within medium zones */ int hosts_large = 0; /* number of hosts within large zones */ int hosts_huge = 0; /* number of hosts within huge zones */ int total_stats[T_ANY+1]; /* total count of resource records per type */ #ifdef justfun char longname[MAXDNAME+1]; /* longest host name found */ int longsize = 0; /* size of longest host name */ #endif bool list_zone(name) input char *name; /* name of zone to process */ { register int n; register int i; int nzones; /* count of delegated zones */ int nhosts; /* count of real host names */ int ndupls; /* count of duplicate hosts */ int nextrs; /* count of extrazone hosts */ int ngates; /* count of gateway hosts */ total_calls += 1; /* update zone processing calls */ /* * Normalize to not have trailing dot, unless it is the root zone. */ n = strlength(name); if (n > 1 && name[n-1] == '.') name[n-1] = '\0'; pr_timestamp("zone processing starting for %s", name); /* * Indicate whether we are processing an in-addr.arpa reverse zone. * In this case we will suppress accumulating host count statistics. */ reverse = indomain(name, ARPA_ROOT, FALSE); /* * Enable various checks in certain circumstances. * This affects processing in print_rrec(). It may need refinement. */ if (addrmode && !reverse) cnamecheck = TRUE; /* * Suppress various checks if working beyond the recursion skip level. * This affects processing in print_rrec(). It may need refinement. */ canonskip = ((recursion_level > skip_level) && !addrmode && !canoncheck) ? TRUE : FALSE; underskip = ((recursion_level > skip_level) && !addrmode && !undercheck) ? TRUE : FALSE; /* * Reset the load/dump switches for zone transfers to their defaults. * These may be overruled hereafter, on a per-zone basis. */ dumping = dumpzone; /* should dump to the cache */ loading = loadzone; /* should load from the cache */ /* * When not loading from the cache, compare the serial numbers in the * cache and in reality, and avoid the transfer if nothing has changed. * In that case, load the zone from the cache instead. * Quit immediately if a quick dump of a single zone was requested. */ if (!loading && compare && compare_soa(name)) { if (verbose) printf("Avoiding zone transfer for %s\n", name); /* all done if just dumping a single zone */ if (dumping && !recursive && quick) return((errorcount == 0) ? TRUE : FALSE); /* load from the cache instead for further processing */ dumping = FALSE; loading = TRUE; } /* * Find the nameservers for the given zone. * Make sure we have an address for at least one nameserver. * We don't need the servers when loading the zone from the local cache, * but we want them anyway if we are going to check the SOA records. */ if (!loading || checkmode) { (void) find_servers(name); if (nservers < 1) { errmsg("No nameservers for %s found", name); if (!loading) return(FALSE); } for (n = 0; n < nservers; n++) if (naddrs[n] > 0) break; if (nservers > 0 && n >= nservers) { errmsg("No addresses of nameservers for %s found", name); if (!loading) return(FALSE); } } /* * Without an explicit server on the command line, the servers we * have looked up are supposed to be authoritative for the zone. */ authserver = (server && !primary) ? FALSE : TRUE; /* * Check SOA records at each of the nameservers if so requested. */ if (checkmode) { do_check(name); total_check += 1; /* update zones processed */ /* all done if maximum recursion level reached */ if (!recursive || (recursion_level >= recursive)) return((errorcount == 0) ? TRUE : FALSE); } /* * The zone transfer for certain zones can be skipped. */ if (skip_transfer(name)) { if (verbose || statistics || checkmode || hostmode) printf("Skipping zone transfer for %s\n", name); return(FALSE); } /* * Ask zone transfer to the nameservers, until one responds. */ pr_timestamp("zone transfer starting for %s", name); total_tries += 1; /* update zone transfer attempts */ if (!do_transfer(name)) return(FALSE); total_zones += 1; /* update successful zone transfers */ pr_timestamp("zone transfer complete for %s", name); /* * Print resource record statistics if so requested. */ if (statistics) print_stats(record_stats, 0, name, querytype, queryclass); /* * Accumulate host count statistics for this zone. * Do this only in modes in which such output would be printed. */ pr_timestamp("accumulate statistics for %s", name); nzones = zonecount; nhosts = 0, ndupls = 0, nextrs = 0, ngates = 0; i = ((verbose && !quick) || statistics || hostmode) ? 0 : hostcount; for (n = i; n < hostcount; n++) { /* skip fake hosts using a very rudimentary test */ if (fakename(hostname(n)) || fakeaddr(hostaddr(n))) continue; #ifdef justfun /* save longest host name encountered so far */ if (verbose && ((i = strlength(hostname(n))) > longsize)) { longsize = i; (void) strcpy(longname, hostname(n)); } #endif /* skip apparent glue records */ if (gluerecord(hostname(n), name, zonename, nzones)) { if (verbose > 1) printf("%s is glue record\n", hostname(n)); continue; } /* otherwise count as host */ nhosts++; /* * Mark hosts not residing directly in the zone as extrazone host. * These have extra label components without further delegation. */ if (!samedomain(hostname(n), name, TRUE)) { nextrs++; if (extrmode || (verbose > 1)) printf("%s is extrazone host\n", hostname(n)); } /* * Mark hosts with more than one address as gateway host. * These are not checked for duplicate addresses. */ if (multaddr(n)) { ngates++; if (gatemode || (verbose > 1)) printf("%s is gateway host\n", hostname(n)); } /* * Compare single address hosts against global list of addresses. * Multiple address hosts are too complicated to handle this way. */ else if (check_dupl(hostaddr(n))) { struct in_addr inaddr; inaddr.s_addr = hostaddr(n); ndupls++; if (duplmode || (verbose > 1)) printf("%s is duplicate host with address %s\n", hostname(n), inet_ntoa(inaddr)); } } pr_timestamp("finished statistics for %s", name); /* * Print statistics for this zone. */ if ((verbose && !quick) || statistics || hostmode) { printf("Encountered %d host%s within %s\n", nhosts, plural(nhosts), name); if ((ndupls > 0) || duplmode || (verbose > 1)) printf("Encountered %d duplicate host%s within %s\n", ndupls, plural(ndupls), name); if ((nextrs > 0) || extrmode || (verbose > 1)) printf("Encountered %d extrazone host%s within %s\n", nextrs, plural(nextrs), name); if ((ngates > 0) || gatemode || (verbose > 1)) printf("Encountered %d gateway host%s within %s\n", ngates, plural(ngates), name); } if (verbose || statistics) printf("Found %d delegated zone%s within %s\n", nzones, plural(nzones), name); /* * Update overall statistics. */ for (i = T_FIRST; i <= T_LAST; i++) total_stats[i] += record_stats[i]; total_hosts += nhosts; /* update total number of hosts */ total_dupls += ndupls; /* update total number of duplicates */ if (nhosts < 1) { zones_empty += 1; } else if (nhosts < 10) { zones_small += 1; hosts_small += nhosts; } else if (nhosts < 100) { zones_medium += 1; hosts_medium += nhosts; } else if (nhosts < 1000) { zones_large += 1; hosts_large += nhosts; } else { zones_huge += 1; hosts_huge += nhosts; } if (!checkmode) total_check += 1; /* update zones processed */ /* * Sort the encountered delegated zones alphabetically. * Note that this precludes further use of the zone_index() function. */ pr_timestamp("sorting child zones for %s", name); if ((nzones > 1) && (recursive || listzones || mxdomains)) qsort((ptr_t *)zonename, nzones, sizeof(char *), compare_name); /* * The names of the hosts were allocated dynamically. */ pr_timestamp("freeing host memory for %s", name); for (n = 0; n < hostcount; n++) xfree(hostname(n)); /* * Check for mailable delegated zones within this zone, based on ordinary MX * lookup, not on the MX info in the zone listing, to reduce zone transfers. */ if (mxdomains) { if (recursion_level == 0) { if (verbose) printf("\n"); if (!get_mxrec(name)) ns_error(name, T_MX, queryclass, server); } for (n = 0; n < nzones; n++) { if (verbose) printf("\n"); if (!get_mxrec(zonename[n])) ns_error(zonename[n], T_MX, queryclass, server); } } /* * Do recursion on delegated zones if requested and any were found. * Temporarily save zonename list, and force allocation of new list. */ if (recursive && (recursion_level < recursive)) { for (n = 0; n < nzones; n++) { char **newzone; /* local copy of list */ newzone = zonename; zonename = NULL; /* allocate new list */ if (verbose || statistics || checkmode || hostmode) printf("\n"); if (listzones) { for (i = 0; i <= recursion_level; i++) printf("%s", (i == 0) ? "\t" : " "); printf("%s\n", newzone[n]); } if (verbose) printf("Entering zone %s\n", newzone[n]); recursion_level++; (void) list_zone(newzone[n]); recursion_level--; zonename = newzone; /* restore */ } } else { if (listzones) { for (n = 0; n < nzones; n++) { for (i = 0; i <= recursion_level; i++) printf("%s", (i == 0) ? "\t" : " "); printf("%s\n", zonename[n]); } } } /* * The names of the delegated zones were allocated dynamically. * The list of delegated zone names was also allocated dynamically. */ pr_timestamp("freeing zone memory for %s", name); for (n = 0; n < nzones; n++) xfree(zonename[n]); if (zonename != NULL) xfree(zonename); zonename = NULL; /* * Print final overall statistics. */ if (recursive && (recursion_level == 0)) { if (verbose || statistics || checkmode || hostmode) printf("\n"); if (statistics) print_stats(total_stats, total_zones, name, querytype, queryclass); if ((verbose && !quick) || statistics || hostmode) printf("Encountered %d host%s in %d zone%s within %s\n", total_hosts, plural(total_hosts), total_zones, plural(total_zones), name); if ((verbose && !quick) || statistics || hostmode) printf("Encountered %d duplicate host%s in %d zone%s within %s\n", total_dupls, plural(total_dupls), total_zones, plural(total_zones), name); if (verbose || statistics || checkmode) printf("Transferred %d zone%s out of %d attempt%s\n", total_zones, plural(total_zones), total_tries, plural(total_tries)); if (verbose || statistics || checkmode) printf("Processed %d zone%s out of %d request%s\n", total_check, plural(total_check), total_calls, plural(total_calls)); #ifdef justfun if (verbose && (longsize > 0)) printf("Longest hostname %s\t%d\n", longname, longsize); #endif if ((verbose && !quick) || statistics || hostmode) { if (zones_empty > 0) printf("Classified %d/%d empty zone%s (%d/%d host%s) within %s\n", zones_empty, total_zones, plural(zones_empty), hosts_empty, total_hosts, plural(hosts_empty), name); if (zones_small > 0) printf("Classified %d/%d small zone%s (%d/%d host%s) within %s\n", zones_small, total_zones, plural(zones_small), hosts_small, total_hosts, plural(hosts_small), name); if (zones_medium > 0) printf("Classified %d/%d medium zone%s (%d/%d host%s) within %s\n", zones_medium, total_zones, plural(zones_medium), hosts_medium, total_hosts, plural(hosts_medium), name); if (zones_large > 0) printf("Classified %d/%d large zone%s (%d/%d host%s) within %s\n", zones_large, total_zones, plural(zones_large), hosts_large, total_hosts, plural(hosts_large), name); if (zones_huge > 0) printf("Classified %d/%d huge zone%s (%d/%d host%s) within %s\n", zones_huge, total_zones, plural(zones_huge), hosts_huge, total_hosts, plural(hosts_huge), name); } } pr_timestamp("zone processing complete for %s", name); /* indicate whether any errors were encountered */ return((errorcount == 0) ? TRUE : FALSE); } /* ** FIND_SERVERS -- Fetch names and addresses of authoritative servers ** ------------------------------------------------------------------ ** ** Returns: ** TRUE if servers could be determined successfully. ** FALSE otherwise. ** ** Inputs: ** The global variable ``server'', if set, contains the ** name of the explicit server to be contacted. ** The global variable ``primary'', if set, indicates ** that we must use the primary nameserver for the zone. ** If both are set simultaneously, the explicit server ** is contacted to retrieve the desired servers. ** ** Outputs: ** The count of nameservers is stored in ``nservers''. ** Names are stored in the nsname[] database. ** Addresses are stored in the ipaddr[] database. ** Address counts are stored in the naddrs[] database. */ bool find_servers(name) input char *name; /* name of zone to find servers for */ { struct hostent *hp; register int n, i; /* * Use the explicit server if given on the command line. * Its addresses are stored in the resolver state struct. * This server may not be authoritative for the given zone. */ if (server && !primary) { (void) strcpy(nsname[0], server); for (i = 0; i < MAXIPADDR && i < _res.nscount; i++) ipaddr[0][i] = nslist(i).sin_addr; naddrs[0] = i; nservers = 1; return(TRUE); } /* * Fetch primary nameserver info if so requested. * Get its name from the SOA record for the zone, and do a regular * host lookup to fetch its addresses. We are assuming here that the * SOA record is a proper one. This is not necessarily true. * Obviously this server should be authoritative. */ if (primary && !server) { char *primaryname; primaryname = get_primary(name); if (primaryname == NULL) { ns_error(name, T_SOA, queryclass, server); nservers = 0; return(FALSE); } hp = geth_byname(primaryname); if (hp == NULL) { ns_error(primaryname, T_A, C_IN, server); nservers = 0; return(FALSE); } primaryname = strncpy(nsname[0], hp->h_name, MAXDNAME); primaryname[MAXDNAME] = '\0'; for (i = 0; i < MAXIPADDR && hp->h_addr_list[i]; i++) ipaddr[0][i] = incopy(hp->h_addr_list[i]); naddrs[0] = i; if (verbose) printf("Found %d address%s for %s\n", naddrs[0], plurale(naddrs[0]), nsname[0]); nservers = 1; return(TRUE); } /* * Otherwise we have to find the nameservers for the zone. * These are supposed to be authoritative, but sometimes we * encounter lame delegations, perhaps due to misconfiguration. */ if (!get_servers(name)) { ns_error(name, T_NS, queryclass, server); nservers = 0; return(FALSE); } /* * Usually we'll get addresses for all the servers in the additional * info section. But in case we don't, look up their addresses. * Addresses could be missing because there is no room in the answer. * No address is present if the name of a server is not canonical. * If we get no addresses by extra query, and this is authoritative, * we flag a lame delegation to that server. */ for (n = 0; n < nservers; n++) { if (naddrs[n] == 0) { hp = geth_byname(nsname[n]); if (hp != NULL) { for (i = 0; i < MAXIPADDR && hp->h_addr_list[i]; i++) ipaddr[n][i] = incopy(hp->h_addr_list[i]); naddrs[n] = i; } if (verbose) printf("Found %d address%s for %s by extra query\n", naddrs[n], plurale(naddrs[n]), nsname[n]); if (hp == NULL) { /* server name lookup failed */ ns_error(nsname[n], T_A, C_IN, server); /* authoritative denial: probably misconfiguration */ if (h_errno == NO_DATA || h_errno == HOST_NOT_FOUND) { if (server == NULL) errmsg("%s has lame delegation to %s", name, nsname[n]); } } if ((hp != NULL) && !sameword(hp->h_name, nsname[n])) pr_warning("%s nameserver %s is not canonical (%s)", name, nsname[n], hp->h_name); } else { if (verbose) printf("Found %d address%s for %s\n", naddrs[n], plurale(naddrs[n]), nsname[n]); } } /* * Issue warning if only one server has been discovered. * This is not an error per se, but not much redundancy in that case. */ if (nservers == 1) pr_warning("%s has only one nameserver %s", name, nsname[0]); return((nservers > 0) ? TRUE : FALSE); } /* ** GET_SERVERS -- Fetch names and addresses of authoritative servers ** ----------------------------------------------------------------- ** ** Returns: ** TRUE if servers could be determined successfully. ** FALSE otherwise. ** ** Side effects: ** The count of nameservers is stored in ``nservers''. ** Names are stored in the nsname[] database. ** Addresses are stored in the ipaddr[] database. ** Address counts are stored in the naddrs[] database. */ bool get_servers(name) input char *name; /* name of zone to find servers for */ { querybuf answer; register int n; bool result; /* result status of action taken */ if (verbose) printf("Finding nameservers for %s ...\n", name); n = get_info(&answer, name, T_NS, queryclass); if (n < 0) return(FALSE); if (verbose > 1) (void) print_info(&answer, n, name, T_NS, queryclass, FALSE); result = get_nsinfo(&answer, n, name, T_NS, queryclass); return(result); } /* ** GET_NSINFO -- Extract nameserver data from nameserver answer buffer ** ------------------------------------------------------------------- ** ** Returns: ** TRUE if the answer buffer was processed successfully. ** FALSE otherwise. ** ** Outputs: ** The count of nameservers is stored in ``nservers''. ** Names are stored in the nsname[] database. ** Addresses are stored in the ipaddr[] database. ** Address counts are stored in the naddrs[] database. */ bool get_nsinfo(answerbuf, answerlen, name, qtype, qclass) input querybuf *answerbuf; /* location of answer buffer */ input int answerlen; /* length of answer buffer */ input char *name; /* name of zone to find servers for */ input int qtype; /* record type we are querying about */ input int qclass; /* record class we are querying about */ { HEADER *bp; int qdcount, ancount, nscount, arcount, rrcount; u_char *msg, *eom; register u_char *cp; register int i; nservers = 0; /* count of nameservers */ bp = (HEADER *)answerbuf; qdcount = ntohs((u_short)bp->qdcount); ancount = ntohs((u_short)bp->ancount); nscount = ntohs((u_short)bp->nscount); arcount = ntohs((u_short)bp->arcount); msg = (u_char *)answerbuf; eom = (u_char *)answerbuf + answerlen; cp = (u_char *)answerbuf + HFIXEDSZ; if (qdcount > 0 && cp < eom) /* should be exactly one record */ { cp = skip_qrec(name, qtype, qclass, cp, msg, eom); if (cp == NULL) return(FALSE); qdcount--; } if (qdcount) { pr_error("invalid qdcount after %s query for %s", pr_type(qtype), name); seth_errno(NO_RECOVERY); return(FALSE); } /* * If the answer is authoritative, the names are found in the * answer section, and the nameserver section is empty. * If not, there may be duplicate names in both sections. * Addresses are found in the additional info section both cases. */ rrcount = ancount + nscount + arcount; while (rrcount > 0 && cp < eom) { char rname[MAXDNAME+1]; char dname[MAXDNAME+1]; int type, class, ttl, dlen; u_char *eor; register int n; struct in_addr inaddr; n = expand_name(name, T_NONE, cp, msg, eom, rname); if (n < 0) return(FALSE); cp += n; n = 3*INT16SZ + INT32SZ; if (check_size(rname, T_NONE, cp, msg, eom, n) < 0) return(FALSE); type = _getshort(cp); cp += INT16SZ; class = _getshort(cp); cp += INT16SZ; ttl = _getlong(cp); cp += INT32SZ; dlen = _getshort(cp); cp += INT16SZ; if (check_size(rname, type, cp, msg, eom, dlen) < 0) return(FALSE); eor = cp + dlen; #ifdef lint if (verbose) printf("%-20s\t%d\t%s\t%s\n", rname, ttl, pr_class(class), pr_type(type)); #endif if ((type == T_NS) && sameword(rname, name)) { n = expand_name(rname, type, cp, msg, eom, dname); if (n < 0) return(FALSE); cp += n; for (i = 0; i < nservers; i++) if (sameword(nsname[i], dname)) break; /* duplicate */ if (i >= nservers && nservers < MAXNSNAME) { (void) strcpy(nsname[nservers], dname); naddrs[nservers] = 0; nservers++; } } else if ((type == T_A) && (dlen == INADDRSZ)) { for (i = 0; i < nservers; i++) if (sameword(nsname[i], rname)) break; /* found */ if (i < nservers && naddrs[i] < MAXIPADDR) { bcopy((char *)cp, (char *)&inaddr, INADDRSZ); ipaddr[i][naddrs[i]] = inaddr; naddrs[i]++; } cp += dlen; } else { /* just ignore other records */ cp += dlen; } if (cp != eor) { pr_error("size error in %s record for %s, off by %s", pr_type(type), rname, dtoa(cp - eor)); seth_errno(NO_RECOVERY); return(FALSE); } rrcount--; } if (rrcount) { pr_error("invalid rrcount after %s query for %s", pr_type(qtype), name); seth_errno(NO_RECOVERY); return(FALSE); } /* set proper status if no answers found */ seth_errno((nservers > 0) ? 0 : TRY_AGAIN); return(TRUE); } /* ** SORT_SERVERS -- Sort set of nameservers according to preference ** --------------------------------------------------------------- ** ** Returns: ** None. ** ** Inputs: ** Set of nameservers as determined by find_servers(). ** The global variable ``prefserver'', if set, contains ** a list of preferred server domains to compare against. ** ** Outputs: ** Stores the preferred nameserver order in nsrank[]. */ void sort_servers() { register int i, j; register int n, pref; register char *p, *q; /* * Initialize the default ranking. */ for (n = 0; n < nservers; n++) { nsrank[n] = n; nspref[n] = 0; } /* * Determine the nameserver preference. * Compare against a list of comma-separated preferred server domains. * Use the maximum value of all comparisons. */ for (q = prefserver, p = q; p != NULL; p = q) { q = index(p, ','); if (q != NULL) *q = '\0'; for (n = 0; n < nservers; n++) { pref = matchlabels(nsname[n], p); if (pref > nspref[n]) nspref[n] = pref; } if (q != NULL) *q++ = ','; } /* * Sort the set according to preference. * Keep the rest as much as possible in original order. */ for (i = 0; i < nservers; i++) { for (j = i + 1; j < nservers; j++) { if (nspref[j] > nspref[i]) { pref = nspref[j]; /* nspref[j] = nspref[i]; */ for (n = j; n > i; n--) nspref[n] = nspref[n-1]; nspref[i] = pref; pref = nsrank[j]; /* nsrank[j] = nsrank[i]; */ for (n = j; n > i; n--) nsrank[n] = nsrank[n-1]; nsrank[i] = pref; } } } } /* ** SKIP_TRANSFER -- Check whether a zone transfer should be skipped ** ---------------------------------------------------------------- ** ** Returns: ** TRUE if a transfer for this zone should be skipped. ** FALSE if the zone transfer should proceed. ** ** Inputs: ** The global variable ``skipzone'', if set, contains ** a list of zone names to be skipped. ** ** Certain zones are known to contain bogus information, and ** can be requested to be excluded from further processing. ** The zone transfer for such zones and its delegated zones ** will be skipped. */ bool skip_transfer(name) input char *name; /* name of zone to process */ { register char *p, *q; bool skip = FALSE; for (q = skipzone, p = q; p != NULL; p = q) { q = index(p, ','); if (q != NULL) *q = '\0'; if (sameword(name, p)) skip = TRUE; if (q != NULL) *q++ = ','; } return(skip); } /* ** DO_CHECK -- Check SOA records at each of the nameservers ** -------------------------------------------------------- ** ** Returns: ** None. ** ** Inputs: ** The count of nameservers is stored in ``nservers''. ** Names are stored in the nsname[] database. ** Addresses are stored in the ipaddr[] database. ** Address counts are stored in the naddrs[] database. ** ** The SOA record of the zone is checked at each nameserver. ** Nameserver recursion is turned off to make sure that the ** answer is authoritative. */ void do_check(name) input char *name; /* name of zone to process */ { res_state_t save_res; /* saved copy of resolver database */ char *save_server; /* saved copy of server name */ register int n; register int i; /* * First check the local cache, if appropriate. */ if (loading && !check_cache(name, "cache")) { /* SOA query failed */ ns_error(name, T_SOA, queryclass, "cache"); } /* * Then continue with each of the nameservers. */ /* save resolver database */ save_res = _res; save_server = server; /* turn off nameserver recursion */ _res.options &= ~RES_RECURSE; for (n = 0; n < nservers; n++) { if (naddrs[n] < 1) continue; /* shortcut */ server = nsname[n]; for (i = 0; i < MAXNS && i < naddrs[n]; i++) { nslist(i).sin_family = AF_INET; nslist(i).sin_port = htons(NAMESERVER_PORT); nslist(i).sin_addr = ipaddr[n][i]; } _res.nscount = i; /* retrieve and check SOA */ if (check_zone(name, server)) continue; /* SOA query failed */ ns_error(name, T_SOA, queryclass, server); /* explicit server failure: possibly data expired */ lameserver = (h_errno == SERVER_FAILURE) ? TRUE : FALSE; /* non-authoritative denial: assume lame delegation */ if (h_errno == NO_RREC || h_errno == NO_HOST) lameserver = TRUE; /* authoritative denial: probably misconfiguration */ if (h_errno == NO_DATA || h_errno == HOST_NOT_FOUND) lameserver = TRUE; /* flag an error if server should not have failed */ if (lameserver && authserver) errmsg("%s has lame delegation to %s", name, server); } /* restore resolver database */ _res = save_res; server = save_server; } /* ** DO_SOA -- Check SOA record at a single nameserver address ** --------------------------------------------------------- ** ** Returns: ** None. ** ** The zone SOA record is checked at one nameserver address. ** Nameserver recursion is turned off to make sure that the ** answer is authoritative. */ void do_soa(name, inaddr, host) input char *name; /* name of zone to process */ input struct in_addr inaddr; /* address of server to be queried */ input char *host; /* name of server to be queried */ { res_state_t save_res; /* saved copy of resolver database */ char *save_server; /* saved copy of server name */ querybuf answer; HEADER *bp; register int n; /* save resolver database */ save_res = _res; save_server = server; /* turn off nameserver recursion */ _res.options &= ~RES_RECURSE; /* substitute explicit server name and address */ server = host; nslist(0).sin_family = AF_INET; nslist(0).sin_port = htons(NAMESERVER_PORT); nslist(0).sin_addr = inaddr; _res.nscount = 1; if (verbose) printf("Asking SOA record for %s ...\n", name); n = get_info(&answer, name, T_SOA, queryclass); if (n < 0) { /* SOA query failed */ ns_error(name, T_SOA, queryclass, server); /* explicit server failure: possibly data expired */ lameserver = (h_errno == SERVER_FAILURE) ? TRUE : FALSE; /* non-authoritative denial: assume lame delegation */ if (h_errno == NO_RREC || h_errno == NO_HOST) lameserver = TRUE; /* authoritative denial: probably misconfiguration */ if (h_errno == NO_DATA || h_errno == HOST_NOT_FOUND) lameserver = TRUE; /* flag an error if server should not have failed */ if (lameserver && authserver) errmsg("%s has lame delegation to %s", name, server); } bp = (HEADER *)&answer; if ((n > 0) && !bp->aa) { if (authserver) pr_error("%s SOA record at %s is not authoritative", name, server); else pr_warning("%s SOA record at %s is not authoritative", name, server); if (authserver) errmsg("%s has lame delegation to %s", name, server); } /* restore resolver database */ _res = save_res; server = save_server; } /* ** DO_TRANSFER -- Perform a zone transfer from any of its nameservers ** ------------------------------------------------------------------ ** ** Returns: ** TRUE if the zone data have been retrieved successfully. ** FALSE if none of the servers responded. ** ** Inputs: ** The count of nameservers is stored in ``nservers''. ** Names are stored in the nsname[] database. ** Addresses are stored in the ipaddr[] database. ** Address counts are stored in the naddrs[] database. */ bool do_transfer(name) input char *name; /* name of zone to do zone xfer for */ { register int n, ns; register int i; /* * When loading the zone from the local cache, just go ahead. */ if (loading) { static struct in_addr inaddr; /* unused */ if (transfer_zone(name, inaddr, "cache")) return(TRUE); ns_error(name, T_AXFR, queryclass, "cache"); return(FALSE); } /* * Ask zone transfer to the nameservers, until one responds. * The list of nameservers is sorted according to preference. * An authoritative server should always respond positively. * If it responds with an error, we may have a lame delegation. * Always retry with the next server to avoid missing entire zones. */ for (sort_servers(), ns = 0; ns < nservers; ns++) { for (n = nsrank[ns], i = 0; i < naddrs[n]; i++) { if (verbose) printf("Trying server %s (%s) ...\n", inet_ntoa(ipaddr[n][i]), nsname[n]); if (transfer_zone(name, ipaddr[n][i], nsname[n])) return(TRUE); /* terminate on cache I/O errors */ if (h_errno == CACHE_ERROR) { errmsg("No cache for %s created", name); return(FALSE); } /* zone transfer failed */ if ((h_errno != TRY_AGAIN) || verbose) ns_error(name, T_AXFR, queryclass, nsname[n]); /* zone transfer request was explicitly refused */ if (h_errno == QUERY_REFUSED) { do_soa(name, ipaddr[n][i], nsname[n]); seth_errno(QUERY_REFUSED); break; } /* explicit server failure: possibly data expired */ lameserver = (h_errno == SERVER_FAILURE) ? TRUE : FALSE; /* non-authoritative denial: assume lame delegation */ if (h_errno == NO_RREC || h_errno == NO_HOST) lameserver = TRUE; /* authoritative denial: probably misconfiguration */ if (h_errno == NO_DATA || h_errno == HOST_NOT_FOUND) lameserver = TRUE; /* flag an error if server should not have failed */ if (lameserver && authserver) errmsg("%s has lame delegation to %s", name, nsname[n]); /* try next server if this one is sick */ if (lameserver) break; /* terminate on irrecoverable errors */ if (h_errno != TRY_AGAIN) return(FALSE); /* in case nameserver not present */ if (errno == ECONNREFUSED) break; } } if (nservers > 0 && ns >= nservers) { if ((h_errno == TRY_AGAIN) && !verbose) ns_error(name, T_AXFR, queryclass, (char *)NULL); } errmsg("No nameservers for %s responded", name); return(FALSE); } /* ** TRANSFER_ZONE -- Wrapper for get_zone() to hide administrative tasks ** -------------------------------------------------------------------- ** ** Returns: ** See get_zone() for details. ** ** Side effects: ** See get_zone() for details. ** ** This routine may be called repeatedly with different server ** addresses, until one of the servers responds. Various items ** must be reset on every try to continue with a clean slate. */ bool transfer_zone(name, inaddr, host) input char *name; /* name of zone to do zone xfer for */ input struct in_addr inaddr; /* address of server to be queried */ input char *host; /* name of server to be queried */ { bool result; register int n; /* * Reset the resource record statistics before each try. */ clear_stats(record_stats); /* * Reset the hash tables of saved resource record information. * These tables are used only during the zone transfer itself. * The zonetab is now also used when filtering glue records afterwards. */ clear_ttltab(); clear_hosttab(); clear_zonetab(); /* * Create temporary cache file if data must be dumped. * In case this fails, the entire zone transfer is cancelled. */ if (dumping && (cache_open(name, TRUE) < 0)) { seth_errno(CACHE_ERROR); return(FALSE); } /* * Perform the actual zone transfer. * All error reporting is done by get_zone(). */ result = get_zone(name, inaddr, host); /* * Move temporary cache file to real cache file in case the transfer * was successful. Otherwise just delete the temporary cache file. * If the cache cannot be created, the transfer is marked to have failed. */ if (dumping && (cache_close(result) < 0)) { seth_errno(CACHE_ERROR); result = FALSE; } /* * On failure to get the zone, free any memory that may have been allocated. * On success it is the responsibility of the caller to free the memory. * The information gathered is used by list_zone() after the zone transfer. */ if (!result) { for (n = 0; n < hostcount; n++) xfree(hostname(n)); for (n = 0; n < zonecount; n++) xfree(zonename[n]); if (zonename != NULL) xfree(zonename); zonename = NULL; } return(result); } /* ** GET_ZONE -- Perform a zone transfer from server at specific address ** ------------------------------------------------------------------- ** ** Returns: ** TRUE if the zone data have been retrieved successfully. ** FALSE if an error occurred (h_errno is set appropriately). ** Set TRY_AGAIN wherever possible to try the next server. ** ** Side effects: ** Stores list of delegated zones found in zonename[], ** and the count of delegated zones in ``zonecount''. ** Stores list of host names found in hostname[], ** and the count of host names in ``hostcount''. ** Updates resource record statistics in record_stats[]. ** This array must have been cleared before. */ bool get_zone(name, inaddr, host) input char *name; /* name of zone to do zone xfer for */ input struct in_addr inaddr; /* address of server to be queried */ input char *host; /* name of server to be queried */ { querybuf query; querybuf answer; HEADER *bp; int ancount; int sock; struct sockaddr_in sin; register int n, i; int nrecords = 0; /* number of records processed */ int npackets = 0; /* number of packets received */ /* clear global counts */ soacount = 0; /* count of SOA records */ zonecount = 0; /* count of delegated zones */ hostcount = 0; /* count of host names */ /* * When loading the zone from the local cache, the cache file must exist. */ if (loading) { if (cache_open(name, FALSE) < 0) { seth_errno(NO_RREC); return(FALSE); } if (verbose) printf("Loading zone from cache for %s ...\n", name); goto start; } /* * Construct query, and connect to the given server. */ seterrno(0); /* reset before querying nameserver */ n = res_mkquery(QUERY, name, queryclass, T_AXFR, (qbuf_t *)NULL, 0, (rrec_t *)NULL, (qbuf_t *)&query, sizeof(querybuf)); if (n < 0) { if (debug) printf("%sres_mkquery failed\n", dbprefix); seth_errno(NO_RECOVERY); return(FALSE); } if (debug) { printf("%sget_zone()\n", dbprefix); pr_query((qbuf_t *)&query, n, stdout); } /* setup destination address */ bzero((char *)&sin, sizeof(sin)); sin.sin_family = AF_INET; sin.sin_port = htons(NAMESERVER_PORT); sin.sin_addr = inaddr; sock = _res_socket(AF_INET, SOCK_STREAM, 0); if (sock < 0) { _res_perror(&sin, host, "socket"); seth_errno(TRY_AGAIN); return(FALSE); } if (_res_connect(sock, &sin, sizeof(sin)) < 0) { if (verbose || debug) _res_perror(&sin, host, "connect"); (void) close(sock); seth_errno(TRY_AGAIN); return(FALSE); } if (verbose) printf("Asking zone transfer for %s ...\n", name); /* * Send the query buffer. */ if (_res_write(sock, &sin, host, (char *)&query, n) < 0) { (void) close(sock); seth_errno(TRY_AGAIN); return(FALSE); } start: /* * Process all incoming packets, usually one record in a separate packet. */ while ((n = loading ? cache_read((char *)&answer, sizeof(querybuf)) : _res_read(sock, &sin, host, (char *)&answer, sizeof(querybuf))) != 0) { if (n < 0) { if (loading) (void) cache_close(FALSE); else (void) close(sock); seth_errno(TRY_AGAIN); return(FALSE); } seterrno(0); /* reset after we got an answer */ if (n < HFIXEDSZ) { pr_error("answer length %s too short during %s for %s from %s", dtoa(n), pr_type(T_AXFR), name, host); if (loading) (void) cache_close(FALSE); else (void) close(sock); seth_errno(TRY_AGAIN); return(FALSE); } if (debug > 1) { printf("%sgot answer, %d bytes:\n", dbprefix, n); pr_query((qbuf_t *)&answer, querysize(n), stdout); } /* * Analyze the contents of the answer and check for errors. * An error can be expected only in the very first packet. * The query section should be empty except in the first packet. * Note the special error status codes for specific failures. */ bp = (HEADER *)&answer; ancount = ntohs((u_short)bp->ancount); if (bp->rcode != NOERROR || ancount == 0) { if (verbose || debug) print_answer(&answer, n); switch (bp->rcode) { case NXDOMAIN: /* distinguish between authoritative or not */ seth_errno(bp->aa ? HOST_NOT_FOUND : NO_HOST); break; case NOERROR: /* distinguish between authoritative or not */ seth_errno(bp->aa ? NO_DATA : NO_RREC); break; case REFUSED: /* special status if zone transfer refused */ seth_errno(QUERY_REFUSED); break; case SERVFAIL: /* special status upon explicit failure */ seth_errno(SERVER_FAILURE); break; default: /* all other errors will cause a retry */ seth_errno(TRY_AGAIN); break; } if (npackets != 0) pr_error("unexpected error during %s for %s from %s", pr_type(T_AXFR), name, host); if (loading) (void) cache_close(FALSE); else (void) close(sock); return(FALSE); } /* valid answer received, avoid buffer overrun */ seth_errno(0); n = querysize(n); /* * The nameserver and additional info section should be empty. * There may be multiple answers in the answer section. */ #ifdef obsolete if (ancount > 1) pr_error("multiple answers during %s for %s from %s", pr_type(T_AXFR), name, host); #endif if (ntohs((u_short)bp->nscount) != 0) pr_error("nonzero nscount during %s for %s from %s", pr_type(T_AXFR), name, host); if (ntohs((u_short)bp->arcount) != 0) pr_error("nonzero arcount during %s for %s from %s", pr_type(T_AXFR), name, host); /* * Valid packet received. Print contents if appropriate. * Specific zone information will be saved by update_zone(). */ npackets += 1; nrecords += ancount; soaname = NULL, subname = NULL, adrname = NULL, address = 0; listhost = host; (void) print_info(&answer, n, name, T_AXFR, queryclass, FALSE); #ifdef notyet /* make answer authoritative if it comes from such server */ if (dumping && authserver) bp->aa = 1; #endif /* * Dump data to cache if so requested. */ if (dumping && (cache_write((char *)&answer, n) < 0)) { (void) close(sock); seth_errno(CACHE_ERROR); return(FALSE); } /* * Terminate upon the second SOA record for this zone. */ if (soacount > 1) break; } /* * Write a zero length trailer to the cache to indicate end-of-file. * This is not strictly necessary if the second SOA marks the end. */ if (dumping && (cache_write((char *)&answer, 0) < 0)) { (void) close(sock); seth_errno(CACHE_ERROR); return(FALSE); } /* * End of zone transfer at second SOA record or zero length read. */ if (loading) (void) cache_close(FALSE); else (void) close(sock); /* * Check for the anomaly that the whole transfer consisted of the * SOA records only. Could occur if we queried the victim of a lame * delegation which happened to have the SOA record present. */ if (nrecords <= soacount) { pr_error("empty zone transfer for %s from %s", name, host); seth_errno(NO_RREC); return(FALSE); } /* * Do an extra check for delegated zones that also have an A record. * Those may have been defined in the child zone, and crept in the * parent zone, or may have been defined as glue records. * This is not necessarily an error, but the host count may be wrong. * Note that an A record for the current zone has been ignored above. * Skip this check if explicitly requested in quick mode, or in case * nothing would be printed anyway in quiet mode. */ i = (quiet || quick) ? zonecount : 0; for (n = i; n < zonecount; n++) { i = host_index(zonename[n], FALSE); #ifdef obsolete for (i = 0; i < hostcount; i++) if (sameword(hostname(i), zonename[n])) break; /* found */ #endif if (i < hostcount) pr_warning("%s has both NS and A records within %s from %s", zonename[n], name, host); } /* * The zone transfer has been successful. */ if (verbose) { printf("Transfer complete, %d record%s received for %s\n", nrecords, plural(nrecords), name); if (npackets != nrecords) printf("Transfer consisted of %d packet%s from %s\n", npackets, plural(npackets), host); } return(TRUE); } /* ** UPDATE_ZONE -- Save zone information during zone listings ** --------------------------------------------------------- ** ** Returns: ** None. ** ** Side effects: ** Stores list of delegated zones found in zonename[], ** and the count of delegated zones in ``zonecount''. ** Stores list of host names found in hostname[], ** and the count of host names in ``hostcount''. ** Stores the count of SOA records in ``soacount''. ** ** This routine is called by print_info() for each resource record. */ void update_zone(name) input char *name; /* name of zone to do zone xfer for */ { char *host = listhost; /* contacted host for zone listings */ register int i; /* * Terminate upon the second SOA record for this zone. */ if (soaname && sameword(soaname, name)) soacount++; /* the nameserver balks on this one */ else if (soaname && !sameword(soaname, name)) pr_warning("extraneous SOA record for %s within %s from %s", soaname, name, host); /* * Save encountered delegated zone name for recursive listing. */ if (subname && indomain(subname, name, FALSE)) { i = zone_index(subname, TRUE); #ifdef obsolete for (i = 0; i < zonecount; i++) if (sameword(zonename[i], subname)) break; /* duplicate */ #endif if (i >= zonecount) { zonename = newlist(zonename, zonecount+1, char *); zonename[zonecount] = newstr(subname); zonecount++; } } /* warn about strange delegated zones */ else if (subname && !indomain(subname, name, TRUE)) pr_warning("extraneous NS record for %s within %s from %s", subname, name, host); /* * Save encountered name of A record for host name count. */ if (adrname && indomain(adrname, name, FALSE) && !reverse) { i = host_index(adrname, TRUE); #ifdef obsolete for (i = 0; i < hostcount; i++) if (sameword(hostname(i), adrname)) break; /* duplicate */ #endif if (i >= hostcount) { if (hostcount >= maxhosts) { maxhosts += MAXHOSTINCR; hostlist = newlist(hostlist, maxhosts, host_data_t); } hostname(hostcount) = newstr(adrname); hostaddr(hostcount) = address; multaddr(hostcount) = FALSE; hostcount++; } else if (address != hostaddr(i)) multaddr(i) = TRUE; } /* check for unauthoritative glue records */ else if (adrname && !indomain(adrname, name, TRUE)) pr_warning("extraneous glue record for %s within %s from %s", adrname, name, host); } /* ** GET_MXREC -- Fetch MX records of a domain ** ----------------------------------------- ** ** Returns: ** TRUE if MX records were found. ** FALSE otherwise. */ bool get_mxrec(name) input char *name; /* domain name to get mx for */ { querybuf answer; register int n; if (verbose) printf("Finding MX records for %s ...\n", name); n = get_info(&answer, name, T_MX, queryclass); if (n < 0) return(FALSE); (void) print_info(&answer, n, name, T_MX, queryclass, FALSE); return(TRUE); } /* ** GET_PRIMARY -- Fetch name of primary nameserver for a zone ** ---------------------------------------------------------- ** ** Returns: ** Pointer to the name of the primary server, if found. ** NULL if the server could not be determined. */ char * get_primary(name) input char *name; /* name of zone to get soa for */ { querybuf answer; register int n; if (verbose) printf("Finding primary nameserver for %s ...\n", name); n = get_info(&answer, name, T_SOA, queryclass); if (n < 0) return(NULL); if (verbose > 1) (void) print_info(&answer, n, name, T_SOA, queryclass, FALSE); soaname = NULL; (void) get_soainfo(&answer, n, name, T_SOA, queryclass); if (soaname == NULL) return(NULL); return(soa.primary); } /* ** CHECK_ZONE -- Fetch and analyze SOA record of a zone ** ---------------------------------------------------- ** ** Returns: ** TRUE if the SOA record was found at the given server. ** FALSE otherwise. ** ** Inputs: ** The global variable ``server'' must contain the name of ** the server that was queried, and the resolver database ** must have been reset with its addresses. */ bool check_zone(name, host) input char *name; /* name of zone to get soa for */ input char *host; /* name of server to be queried */ { querybuf answer; register int n; if (verbose) printf("Checking SOA for %s at %s ...\n", name, host); else if (authserver) printf("%-20s\tNS\t%s\n", name, host); else printf("%-20s\t(%s)\n", name, host); n = get_info(&answer, name, T_SOA, queryclass); if (n < 0) return(FALSE); if (verbose > 1) (void) print_info(&answer, n, name, T_SOA, queryclass, FALSE); soaname = NULL; (void) get_soainfo(&answer, n, name, T_SOA, queryclass); if (soaname == NULL) return(FALSE); check_soa(&answer, name, host); return(TRUE); } /* ** CHECK_CACHE -- Fetch and analyze SOA record of a zone from the cache ** -------------------------------------------------------------------- ** ** Returns: ** TRUE if the SOA record was found at the cache. ** FALSE otherwise. ** ** The very first record is retrieved from the cache. ** Note that in the query section the type is AXFR, not SOA. ** This implies that we cannot call print_info() here. */ bool check_cache(name, host) input char *name; /* name of zone to get soa for */ input char *host; /* name of server to be queried */ { querybuf answer; register int n; if (verbose) printf("Checking SOA for %s at %s ...\n", name, host); else printf("%-20s\t(%s)\n", name, host); n = load_soa(&answer, name); if (n < 0) return(FALSE); soaname = NULL; (void) get_soainfo(&answer, n, name, T_AXFR, queryclass); if (soaname == NULL) return(FALSE); check_soa(&answer, name, host); return(TRUE); } /* ** COMPARE_SOA -- Compare SOA serial numbers in cache and reality ** -------------------------------------------------------------- ** ** Returns: ** TRUE if both serial numbers exist, and are the same, ** or in case a load from the cache is forced. ** FALSE otherwise. ** ** Note. The live serial number is retrieved via an ordinary ** regular query, and not directly from any of the nameservers ** because we have not looked up them yet. It may need refinement. */ bool compare_soa(name) input char *name; /* name of zone to get soa for */ { int serial1, serial2; querybuf answer; register int n; if (verbose) printf("Comparing SOA serial for %s ...\n", name); /* * Fetch the serial number from the cache. */ n = load_soa(&answer, name); if (n < 0) goto error1; soaname = NULL; (void) get_soainfo(&answer, n, name, T_AXFR, queryclass); if (soaname == NULL) goto error1; serial1 = soa.serial; /* * Force a load from the cache in case it is more recent than * a certain reference time in the past, if specified. */ if (loadtime > 0 && cachetime > loadtime) return(TRUE); /* * Fetch the live serial number. */ n = get_info(&answer, name, T_SOA, queryclass); if (n < 0) goto error2; soaname = NULL; (void) get_soainfo(&answer, n, name, T_SOA, queryclass); if (soaname == NULL) goto error2; serial2 = soa.serial; /* * Report the result. */ return((serial1 == serial2) ? TRUE : FALSE); error1: /* no serial number from the cache */ if (verbose) ns_error(name, T_SOA, queryclass, "cache"); return(FALSE); error2: /* no live serial number found */ if (verbose) ns_error(name, T_SOA, queryclass, server); return(FALSE); } /* ** GET_SOAINFO -- Extract SOA data from nameserver answer buffer ** ------------------------------------------------------------- ** ** Returns: ** TRUE if the answer buffer was processed successfully. ** FALSE otherwise. ** ** Outputs: ** The global struct ``soa'' is filled with the soa data. ** ** Side effects: ** Sets ``soaname'' if there is a valid SOA record. ** This variable must have been cleared before calling ** get_soainfo() and may be checked afterwards. */ bool get_soainfo(answerbuf, answerlen, name, qtype, qclass) input querybuf *answerbuf; /* location of answer buffer */ input int answerlen; /* length of answer buffer */ input char *name; /* name of zone to get soa for */ input int qtype; /* record type we are querying about */ input int qclass; /* record class we are querying about */ { HEADER *bp; int qdcount, ancount; u_char *msg, *eom; register u_char *cp; bp = (HEADER *)answerbuf; qdcount = ntohs((u_short)bp->qdcount); ancount = ntohs((u_short)bp->ancount); msg = (u_char *)answerbuf; eom = (u_char *)answerbuf + answerlen; cp = (u_char *)answerbuf + HFIXEDSZ; if (qdcount > 0 && cp < eom) /* should be exactly one record */ { cp = skip_qrec(name, qtype, qclass, cp, msg, eom); if (cp == NULL) return(FALSE); qdcount--; } if (qdcount) { pr_error("invalid qdcount after %s query for %s", pr_type(qtype), name); seth_errno(NO_RECOVERY); return(FALSE); } /* * Check answer section only. * Check that answers match the requested zone. Ignore other entries. * The nameserver section may contain the nameservers for the zone, * and the additional section their addresses, but not guaranteed. * Those sections are usually empty for authoritative answers. */ while (ancount > 0 && cp < eom) { char rname[MAXDNAME+1]; int type, class, ttl, dlen; u_char *eor; register int n; n = expand_name(name, T_NONE, cp, msg, eom, rname); if (n < 0) return(FALSE); cp += n; n = 3*INT16SZ + INT32SZ; if (check_size(rname, T_NONE, cp, msg, eom, n) < 0) return(FALSE); type = _getshort(cp); cp += INT16SZ; class = _getshort(cp); cp += INT16SZ; ttl = _getlong(cp); cp += INT32SZ; dlen = _getshort(cp); cp += INT16SZ; if (check_size(rname, type, cp, msg, eom, dlen) < 0) return(FALSE); eor = cp + dlen; #ifdef lint if (verbose) printf("%-20s\t%d\t%s\t%s\n", rname, ttl, pr_class(class), pr_type(type)); #endif if ((type == T_SOA) && sameword(rname, name)) { n = expand_name(rname, type, cp, msg, eom, soa.primary); if (n < 0) return(FALSE); cp += n; n = expand_name(rname, type, cp, msg, eom, soa.hostmaster); if (n < 0) return(FALSE); cp += n; n = 5*INT32SZ; if (check_size(rname, type, cp, msg, eor, n) < 0) return(FALSE); soa.serial = _getlong(cp); cp += INT32SZ; soa.refresh = _getlong(cp); cp += INT32SZ; soa.retry = _getlong(cp); cp += INT32SZ; soa.expire = _getlong(cp); cp += INT32SZ; soa.defttl = _getlong(cp); cp += INT32SZ; /* valid complete soa record found */ soaname = strcpy(soanamebuf, rname); } else { /* just ignore other records */ cp += dlen; } if (cp != eor) { pr_error("size error in %s record for %s, off by %s", pr_type(type), rname, dtoa(cp - eor)); seth_errno(NO_RECOVERY); return(FALSE); } ancount--; } if (ancount) { pr_error("invalid ancount after %s query for %s", pr_type(qtype), name); seth_errno(NO_RECOVERY); return(FALSE); } /* set proper status if no answers found */ seth_errno((soaname != NULL) ? 0 : TRY_AGAIN); return(TRUE); } /* ** LOAD_SOA -- Load the SOA record of a zone from the cache ** -------------------------------------------------------- ** ** Returns: ** Length of answer buffer, if obtained. ** -1 if no answer (h_errno is set appropriately). ** ** This just reads the very first record from the cache. */ int load_soa(answerbuf, name) output querybuf *answerbuf; /* location of buffer to store answer */ input char *name; /* name of zone to check soa for */ { register int n; if (cache_open(name, FALSE) < 0) { seth_errno(NO_RREC); return(-1); } n = cache_read((char *)answerbuf, sizeof(querybuf)); if (n < 0) { (void) cache_close(FALSE); seth_errno(TRY_AGAIN); return(-1); } (void) cache_close(FALSE); seth_errno(0); return(n); } /* ** CHECK_SOA -- Analyze retrieved SOA records of a zone ** ---------------------------------------------------- ** ** Returns: ** None. ** ** Inputs: ** The global variable ``server'' must contain the name of ** the server that was queried, and the resolver database ** must have been reset with its addresses. ** The global struct ``soa'' must contain the soa data. */ void check_soa(answerbuf, name, host) input querybuf *answerbuf; /* location of answer buffer */ input char *name; /* name of zone to check soa for */ input char *host; /* name of server to be queried */ { static char oldnamebuf[MAXDNAME+1]; static char *oldname = NULL; /* previous name of zone */ static char *oldhost = NULL; /* previous name of server */ static soa_data_t oldsoa; /* previous soa data */ register int n; HEADER *bp; /* * Print the various SOA fields in abbreviated form. * Values are actually unsigned, but we print them as signed integers, * apart from the serial which really becomes that big sometimes. * In the latter case we print a warning below. */ printf("%s\t%s\t(%u %d %d %d %d)\n", soa.primary, soa.hostmaster, (unsigned)soa.serial, soa.refresh, soa.retry, soa.expire, soa.defttl); /* * We are supposed to have queried an authoritative nameserver, and since * nameserver recursion has been turned off, answer must be authoritative. * An answer retrieved from the local cache is never marked authoritative. */ bp = (HEADER *)answerbuf; if (!bp->aa && !sameword(host, "cache")) { if (authserver) pr_error("%s SOA record at %s is not authoritative", name, host); else pr_warning("%s SOA record at %s is not authoritative", name, host); if (authserver) errmsg("%s has lame delegation to %s", name, host); } /* * Check whether we are switching to a new zone. * The old name must have been saved in static storage. */ if ((oldname != NULL) && !sameword(name, oldname)) oldname = NULL; /* * Make few timer consistency checks only for the first one in a series. * Compare the primary field against the list of authoritative servers. * Explicitly check the hostmaster field for illegal characters ('@'). * Yell if the serial has the high bit set (not always intentional). * Make sanity checks for refresh and retry times. * Check for bizarre expire values. */ if (oldname == NULL) { for (n = 0; n < nservers; n++) if (sameword(soa.primary, nsname[n])) break; /* found */ if ((n >= nservers) && authserver) pr_warning("%s SOA primary %s is not advertised via NS", name, soa.primary); if (!valid_name(soa.primary, FALSE, FALSE, FALSE)) pr_warning("%s SOA primary %s has illegal name", name, soa.primary); if (!valid_name(soa.hostmaster, FALSE, TRUE, FALSE)) pr_warning("%s SOA hostmaster %s has illegal mailbox", name, soa.hostmaster); if (bitset(0x80000000, soa.serial)) pr_warning("%s SOA serial has high bit set", name); if (soa.retry > soa.refresh) pr_warning("%s SOA retry exceeds refresh", name); if (soa.refresh + soa.retry > soa.expire) pr_warning("%s SOA refresh+retry exceeds expire", name); if (soa.expire < 1 * 7 * 24 * 3600) pr_warning("%s SOA expire is less than 1 week (%s)", name, pr_time(soa.expire, FALSE)); if (soa.expire > 26 * 7 * 24 * 3600) pr_warning("%s SOA expire is more than 6 months (%s)", name, pr_time(soa.expire, FALSE)); } /* * Compare various fields with those of the previous query, if any. * Different serial numbers may be present if secondaries have not yet * refreshed the data from the primary. Issue only a warning in that case. */ if (oldname != NULL) { if (!sameword(soa.primary, oldsoa.primary)) pr_error("%s and %s have different primary for %s", host, oldhost, name); if (!sameword(soa.hostmaster, oldsoa.hostmaster)) pr_error("%s and %s have different hostmaster for %s", host, oldhost, name); if (soa.serial != oldsoa.serial) pr_warning("%s and %s have different serial for %s", host, oldhost, name); if (soa.refresh != oldsoa.refresh) pr_error("%s and %s have different refresh for %s", host, oldhost, name); if (soa.retry != oldsoa.retry) pr_error("%s and %s have different retry for %s", host, oldhost, name); if (soa.expire != oldsoa.expire) pr_error("%s and %s have different expire for %s", host, oldhost, name); if (soa.defttl != oldsoa.defttl) pr_error("%s and %s have different defttl for %s", host, oldhost, name); } /* * Save the current information. */ oldname = strcpy(oldnamebuf, name); oldhost = host; oldsoa = soa; } /* ** CHECK_DUPL -- Check global address list for duplicates ** ------------------------------------------------------ ** ** Returns: ** TRUE if the given host address already exists. ** FALSE otherwise. ** ** Side effects: ** Adds the host address to the list if not present. ** ** The information in this table is global, and is not cleared. */ #define AHASHSIZE 0x2000 #define AHASHMASK 0x1fff typedef struct addr_tab { ipaddr_t *addrlist; /* global list of addresses */ int addrcount; /* count of global addresses */ } addr_tab_t; addr_tab_t addrtab[AHASHSIZE]; /* hash list of global addresses */ bool check_dupl(addr) input ipaddr_t addr; /* address of host to check */ { register int i; register addr_tab_t *s; s = &addrtab[ntohl(addr) & AHASHMASK]; for (i = 0; i < s->addrcount; i++) if (s->addrlist[i] == addr) return(TRUE); /* duplicate */ s->addrlist = newlist(s->addrlist, s->addrcount+1, ipaddr_t); s->addrlist[s->addrcount] = addr; s->addrcount++; return(FALSE); } /* ** CHECK_TTL -- Check list of records for different ttl values ** ----------------------------------------------------------- ** ** Returns: ** TRUE if the ttl value matches the first record ** already listed with the same name/type/class. ** FALSE only when the first discrepancy is found. ** ** Side effects: ** Adds the record data to the list if not present. */ typedef struct ttl_tab { struct ttl_tab *next; /* next entry in chain */ char *name; /* name of resource record */ int type; /* resource record type */ int class; /* resource record class */ int ttl; /* time_to_live value */ int count; /* count of different ttl values */ } ttl_tab_t; ttl_tab_t *ttltab[HASHSIZE]; /* hash list of record info */ bool check_ttl(name, type, class, ttl) input char *name; /* resource record name */ input int type, class, ttl; /* resource record fixed values */ { register ttl_tab_t *s; register ttl_tab_t **ps; register unsigned int hfunc; register char *p; register char c; /* * Compute the hash function for this resource record. * Look it up in the appropriate hash chain. */ for (hfunc = type, p = name; (c = *p) != '\0'; p++) { hfunc = ((hfunc << 1) ^ (lowercase(c) & 0377)) % HASHSIZE; } for (ps = &ttltab[hfunc]; (s = *ps) != NULL; ps = &s->next) { if (s->type != type || s->class != class) continue; if (sameword(s->name, name)) break; } /* * Allocate new entry if not found. */ if (s == NULL) { /* ps = &ttltab[hfunc]; */ s = newstruct(ttl_tab_t); /* initialize new entry */ s->name = newstr(name); s->type = type; s->class = class; s->ttl = ttl; s->count = 0; /* link it in */ s->next = *ps; *ps = s; } /* * Check whether the ttl value matches the first recorded one. * If not, signal only the first discrepancy encountered, so * only one warning message will be printed. */ if (s->ttl == ttl) return(TRUE); s->count += 1; return((s->count == 1) ? FALSE : TRUE); } /* ** CLEAR_TTLTAB -- Clear resource record list for ttl checking ** ----------------------------------------------------------- ** ** Returns: ** None. ** ** An entry on the hash list, and the host name in each ** entry, have been allocated in dynamic memory. ** ** The information in this table is on a per-zone basis. ** It must be cleared before any subsequent zone transfers. */ void clear_ttltab() { register int i; register ttl_tab_t *s, *t; for (i = 0; i < HASHSIZE; i++) { if (ttltab[i] != NULL) { /* free chain of entries */ for (t = NULL, s = ttltab[i]; s != NULL; s = t) { t = s->next; xfree(s->name); xfree(s); } /* reset hash chain */ ttltab[i] = NULL; } } } /* ** HOST_INDEX -- Check list of host names for name being present ** ------------------------------------------------------------- ** ** Returns: ** Index into hostname[] table, if found. ** Current ``hostcount'' value, if not found. ** ** Side effects: ** May add an entry to the hash list if not present. ** ** A linear search through the master table becomes very ** costly for zones with more than a few thousand hosts. ** Maintain a hash list with indexes into the master table. ** Caller should update the master table after this call. */ typedef struct host_tab { struct host_tab *next; /* next entry in chain */ int slot; /* slot in host name table */ } host_tab_t; host_tab_t *hosttab[HASHSIZE]; /* hash list of host name info */ int host_index(name, enter) input char *name; /* the host name to check */ input bool enter; /* add to table if not found */ { register host_tab_t *s; register host_tab_t **ps; register unsigned int hfunc; register char *p; register char c; /* * Compute the hash function for this host name. * Look it up in the appropriate hash chain. */ for (hfunc = 0, p = name; (c = *p) != '\0'; p++) { hfunc = ((hfunc << 1) ^ (lowercase(c) & 0377)) % HASHSIZE; } for (ps = &hosttab[hfunc]; (s = *ps) != NULL; ps = &s->next) { if (s->slot >= hostcount) continue; if (sameword(hostname(s->slot), name)) break; } /* * Allocate new entry if not found. */ if ((s == NULL) && enter) { /* ps = &hosttab[hfunc]; */ s = newstruct(host_tab_t); /* initialize new entry */ s->slot = hostcount; /* link it in */ s->next = *ps; *ps = s; } return((s != NULL) ? s->slot : hostcount); } /* ** CLEAR_HOSTTAB -- Clear hash list for host name checking ** ------------------------------------------------------- ** ** Returns: ** None. ** ** A hash list entry has been allocated in dynamic memory. ** ** The information in this table is on a per-zone basis. ** It must be cleared before any subsequent zone transfers. */ void clear_hosttab() { register int i; register host_tab_t *s, *t; for (i = 0; i < HASHSIZE; i++) { if (hosttab[i] != NULL) { /* free chain of entries */ for (t = NULL, s = hosttab[i]; s != NULL; s = t) { t = s->next; xfree(s); } /* reset hash chain */ hosttab[i] = NULL; } } } /* ** ZONE_INDEX -- Check list of zone names for name being present ** ------------------------------------------------------------- ** ** Returns: ** Index into zonename[] table, if found. ** Current ``zonecount'' value, if not found. ** ** Side effects: ** May add an entry to the hash list if not present. ** ** A linear search through the master table becomes very ** costly for more than a few thousand delegated zones. ** Maintain a hash list with indexes into the master table. ** Caller should update the master table after this call. */ typedef struct zone_tab { struct zone_tab *next; /* next entry in chain */ int slot; /* slot in zone name table */ } zone_tab_t; zone_tab_t *zonetab[HASHSIZE]; /* hash list of zone name info */ int zone_index(name, enter) input char *name; /* the zone name to check */ input bool enter; /* add to table if not found */ { register zone_tab_t *s; register zone_tab_t **ps; register unsigned int hfunc; register char *p; register char c; /* * Compute the hash function for this zone name. * Look it up in the appropriate hash chain. */ for (hfunc = 0, p = name; (c = *p) != '\0'; p++) { hfunc = ((hfunc << 1) ^ (lowercase(c) & 0377)) % HASHSIZE; } for (ps = &zonetab[hfunc]; (s = *ps) != NULL; ps = &s->next) { if (s->slot >= zonecount) continue; if (sameword(zonename[s->slot], name)) break; } /* * Allocate new entry if not found. */ if ((s == NULL) && enter) { /* ps = &zonetab[hfunc]; */ s = newstruct(zone_tab_t); /* initialize new entry */ s->slot = zonecount; /* link it in */ s->next = *ps; *ps = s; } return((s != NULL) ? s->slot : zonecount); } /* ** CLEAR_ZONETAB -- Clear hash list for zone name checking ** ------------------------------------------------------- ** ** Returns: ** None. ** ** A hash list entry has been allocated in dynamic memory. ** ** The information in this table is on a per-zone basis. ** It must be cleared before any subsequent zone transfers. */ void clear_zonetab() { register int i; register zone_tab_t *s, *t; for (i = 0; i < HASHSIZE; i++) { if (zonetab[i] != NULL) { /* free chain of entries */ for (t = NULL, s = zonetab[i]; s != NULL; s = t) { t = s->next; xfree(s); } /* reset hash chain */ zonetab[i] = NULL; } } } /* ** CHECK_CANON -- Check list of domain names for name being canonical ** ------------------------------------------------------------------ ** ** Returns: ** Nonzero if the name is definitely not canonical. ** 0 if it is canonical, or if it remains undecided. ** ** Side effects: ** Adds the domain name to the list if not present. ** ** The information in this table is global, and is not cleared ** (which may be necessary if the checking algorithm changes). */ typedef struct canon_tab { struct canon_tab *next; /* next entry in chain */ char *name; /* domain name */ int status; /* nonzero if not canonical */ } canon_tab_t; canon_tab_t *canontab[HASHSIZE]; /* hash list of domain name info */ int check_canon(name) input char *name; /* the domain name to check */ { register canon_tab_t *s; register canon_tab_t **ps; register unsigned int hfunc; register char *p; register char c; /* * Compute the hash function for this domain name. * Look it up in the appropriate hash chain. */ for (hfunc = 0, p = name; (c = *p) != '\0'; p++) { hfunc = ((hfunc << 1) ^ (lowercase(c) & 0377)) % HASHSIZE; } for (ps = &canontab[hfunc]; (s = *ps) != NULL; ps = &s->next) { if (sameword(s->name, name)) break; } /* * Allocate new entry if not found. * Only then is the actual check carried out. */ if (s == NULL) { /* ps = &canontab[hfunc]; */ s = newstruct(canon_tab_t); /* initialize new entry */ s->name = newstr(name); s->status = canonical(name); /* link it in */ s->next = *ps; *ps = s; } return(s->status); }