[comp.protocols.tcp-ip.domains] Removing unneeded glue records

he@spurv.runit.sintef.no (Havard Eidnes) (02/04/91)

We have had some problems with "undesired information leakage" in
BIND.4.8.3.  Let me illustrate with the following situation:

Two name servers: A and B, registered in unrelated domains
Two domains: dom.A and dom.B

dom.A	authoritative copy on A, secondary on B
dom.B	authoritative copy on B, secondary on A

You decide to change the IP address of name server A.  You will probably
have a hard time stomping out the RR for the old A information. The
reason is that during a zone transfer, BIND insists on sending glue
records (containing A information) even though the glue record is for a
name outside of the domain the zone contains. It may fetch this old A
record from anywhere, including from cached-up information (making the
need for a secondary-NS-loop like the above unneccessary for this
misfeature to bite). In order to get rid of this stale record in the
domain name servers you will have to simultaneously do this on both name
servers:

stop name server
remove secondary zone data (to force a reload)
start name server

For various reasons this may be hard to do. Instead, I have developed a
fix to BIND version 4.8.3 so that BIND will only supply glue records
that are truly needed (eg. the NS in question is located in a subdomain
of the "current" domain name) when doing a zone transfer. The fix
follows below, possibly with tabs converted to spaces.

I have not checked with the RFCs whether this is allowed behaviour, but
my experience with the DNS says this makes sense. Comments gladly accepted.

- Havard

------------------------------ Fix to BIND 4.8.3. Apply with "patch -p0 -l"

*** /tmp/,RCSt1a21126   Sun Feb  3 23:41:34 1991
--- ns_req.c    Sat Feb  2 12:54:09 1991
***************
*** 991,994 ****
--- 991,995 ----
  	char *fname;
  	char dname[MAXDNAME];
+ 	char gname[MAXDNAME];
  	HEADER *hp = (HEADER *) msg;
  	int fndns;
***************
*** 1037,1040 ****
--- 1038,1052 ----
  			if (gnp == NULL || fname != dp->d_data)
  				continue;
+ 			/* Do not send glue record unless we delegate to a */
+ 			/* domain name in this domain. This is an attempt at */
+ 			/* preventing old RR's to circulate between domain */
+ 			/* name servers that provide secondary service for */
+ 			/* each others zones. */
+ 			getname(gnp, gname, sizeof(gname));
+ 			n = strlen(gname) - strlen(dname);
+ 			if (n < 0) /* can not possibly be a subdomain */
+ 				continue;
+ 			if (strcasecmp(dname, gname + n) != 0)
+ 				continue;
  			for(gdp=gnp->n_data; gdp != NULL; gdp=gdp->d_next) {
  				if (gdp->d_type != T_A || stale(gdp))

del@algol.mlb.semi.harris.com (Don Lewis) (02/05/91)

In article <1991Feb3.225247.14168@ugle.unit.no> he@idt.unit.no (Havard Eidnes) writes:
>We have had some problems with "undesired information leakage" in
>BIND.4.8.3.  Let me illustrate with the following situation:
>
>Two name servers: A and B, registered in unrelated domains
>Two domains: dom.A and dom.B
>
>dom.A	authoritative copy on A, secondary on B
>dom.B	authoritative copy on B, secondary on A
>
>You decide to change the IP address of name server A.  You will probably
>have a hard time stomping out the RR for the old A information. The
>reason is that during a zone transfer, BIND insists on sending glue
>records (containing A information) even though the glue record is for a
>name outside of the domain the zone contains. It may fetch this old A
>record from anywhere, including from cached-up information (making the
>need for a secondary-NS-loop like the above unneccessary for this
>misfeature to bite). In order to get rid of this stale record in the
>domain name servers you will have to simultaneously do this on both name
>servers:
>
>stop name server
>remove secondary zone data (to force a reload)
>start name server
>

Another way is to:
	stop name server A
	update A's address in dom.A (and update serial number).
	remove glue record for A from dom.B's secondary zone file on A
	    (leaving serial number alone)
	restart name server on A

The next zone transfer of dom.A from A -> B will update A's address on B,
and subsequent B -> A transfers of dom.B will include the correct address.

>For various reasons this may be hard to do. Instead, I have developed a
>fix to BIND version 4.8.3 so that BIND will only supply glue records
>that are truly needed (eg. the NS in question is located in a subdomain
>of the "current" domain name) when doing a zone transfer. The fix
>follows below, possibly with tabs converted to spaces.

I made a similar change a while ago.  My patch is a bit fancier
(and much more complicated).  It has the advantage that it only includes
the extra A records if they reside below an NS RR delegating authority
to a subzone.


*** ORIG/ns_req.c	Wed Jun 27 18:39:46 1990
--- ns_req.c	Fri Feb  1 23:01:23 1991
***************
*** 975,984 ****
  /*
   * Do a zone transfer. SOA record already sent.
   */
! doaxfr(np, rfp, isroot)
  	register struct namebuf *np;
  	FILE *rfp;
  	int isroot;
  {
  	register struct databuf *dp;
  	register int n;
--- 976,986 ----
  /*
   * Do a zone transfer. SOA record already sent.
   */
! doaxfr(np, rfp, isroot, top)
  	register struct namebuf *np;
  	FILE *rfp;
  	int isroot;
+ 	struct namebuf *top;
  {
  	register struct databuf *dp;
  	register int n;
***************
*** 985,990 ****
--- 987,994 ----
  	struct hashbuf *htp;
  	struct databuf *gdp;	/* glue databuf */
  	struct namebuf *gnp;	/* glue namebuf */
+ 	struct namebuf *tnp;
+ 	struct databuf *tdp;
  	struct namebuf **npp, **nppend;
  	char msg[PACKETSZ];
  	char *cp;
***************
*** 1010,1060 ****
  	cp = msg + sizeof(HEADER);
  	getname(np, dname, sizeof(dname));
  
! 	/* first do data records */
  	for (dp = np->n_data; dp != NULL; dp = dp->d_next) {
  		/*
  		 * Skip the root SOA record (marks end of data);
  		 * don't send SOA for subdomains, as we're not sending them.
  		 */
! 		if (dp->d_type == T_SOA)
  			continue;
- 		if (dp->d_type == T_NS)
- 			fndns = 1;
  		if (dp->d_zone == 0 || stale(dp))
  			continue;
  		if ((n = make_rr(dname, dp, cp, sizeof(msg)-sizeof(HEADER), 0)) < 0)
  			continue;
  		fwritemsg(rfp, msg, n + sizeof(HEADER));
- 
- 		if (dp->d_type == T_NS) {
- 			/*  Glue the sub domains together by sending 
- 			 *  the address records for the sub domain
- 			 *  name servers along.
- 			 */
-  			htp = hashtab;
- 			cp = msg + sizeof(HEADER);
-  			gnp = nlookup(dp->d_data, &htp, &fname, 0);
-  			if (gnp == NULL || fname != dp->d_data)
-  				continue;
-  			for(gdp=gnp->n_data; gdp != NULL; gdp=gdp->d_next) {
-  			    if (gdp->d_type != T_A || stale(gdp))
-  				continue;
-  			    if ((n = make_rr(fname, gdp, cp,
-  		    		sizeof(msg)-sizeof(HEADER), 0)) < 0)
-  				continue;
-  			    fwritemsg(rfp, msg, n + sizeof(HEADER));
-  			}
-  		}
  	}
  
! 	/* next do subdomains, unless delegated */
! 	if ((isroot == 0 && fndns) || np->n_hash == NULL)
  		return;
  	npp = np->n_hash->h_tab;
  	nppend = npp + np->n_hash->h_size;
  	while (npp < nppend) {
  		for (np = *npp++; np != NULL; np = np->n_next) {
! 			doaxfr(np, rfp, 0);
  		}
  	}
  #ifdef DEBUG
--- 1014,1090 ----
  	cp = msg + sizeof(HEADER);
  	getname(np, dname, sizeof(dname));
  
! 	/* first do the NS records */
  	for (dp = np->n_data; dp != NULL; dp = dp->d_next) {
+ 	    if (dp->d_type == T_NS) {
+ 		fndns = 1;
+ 		if ((n = make_rr(dname, dp, cp, sizeof(msg)-sizeof(HEADER), 0)) < 0)
+ 			continue;
+ 		fwritemsg(rfp, msg, n + sizeof(HEADER));
+ 		if (!isroot) {
+ 		    /*  Glue the sub domains together by sending 
+ 		     *  the address records for the sub domain
+ 		     *  name servers along if they belong at
+ 		     *  or below the subdomain
+ 		     */
+  		    htp = hashtab;
+ 		    cp = msg + sizeof(HEADER);
+  		    gnp = nlookup(dp->d_data, &htp, &fname, 0);
+  		    if (gnp == NULL || fname != dp->d_data)
+  			continue;
+ 		    for (tnp = gnp; tnp != NULL; tnp = tnp->n_parent)
+ 			if ( tnp == top )
+ 			    break;
+ 		    if ( tnp == NULL )
+ 			continue;  /* name server is not in a (sub)domain */
+ 		    for (tnp = gnp; tnp != top; tnp = tnp->n_parent) {
+ 			for (tdp = tnp->n_data; tdp != NULL; tdp = tdp->d_next)
+ 			    if (tdp->d_type == T_NS)
+ 				break;
+ 			if (tdp != NULL)
+ 			    break;
+ 		    }
+ 		    if (tnp == top)
+ 			continue;  /* name server is not in a subdomain */
+  		    for(gdp=gnp->n_data; gdp != NULL; gdp=gdp->d_next) {
+  			if (gdp->d_type != T_A || stale(gdp))
+  			    continue;
+  			if ((n = make_rr(fname, gdp, cp,
+  		    	    sizeof(msg)-sizeof(HEADER), 0)) < 0)
+ 	 		    continue;
+  			fwritemsg(rfp, msg, n + sizeof(HEADER));
+  		    }
+ 		}
+ 	    }
+ 	}
+ 	/* no need to send anything else because of delegation */
+ 	if (!isroot && fndns)
+ 		return;
+ 
+ 	/* do the rest of the data records */
+ 	for (dp = np->n_data; dp != NULL; dp = dp->d_next) {
  		/*
  		 * Skip the root SOA record (marks end of data);
  		 * don't send SOA for subdomains, as we're not sending them.
+ 		 * Skip the NS record because we did it first
  		 */
! 		if (dp->d_type == T_SOA || dp->d_type == T_NS)
  			continue;
  		if (dp->d_zone == 0 || stale(dp))
  			continue;
  		if ((n = make_rr(dname, dp, cp, sizeof(msg)-sizeof(HEADER), 0)) < 0)
  			continue;
  		fwritemsg(rfp, msg, n + sizeof(HEADER));
  	}
  
! 	/* finally do subdomains */
! 	if (np->n_hash == NULL)
  		return;
  	npp = np->n_hash->h_tab;
  	nppend = npp + np->n_hash->h_size;
  	while (npp < nppend) {
  		for (np = *npp++; np != NULL; np = np->n_next) {
! 			doaxfr(np, rfp, 0, top);
  		}
  	}
  #ifdef DEBUG
***************
*** 1327,1333 ****
  		if (fdstat != -1)
  			(void) fcntl(qsp->s_rfd, F_SETFL, fdstat & ~FNDELAY);
  		fwritemsg(rfp, msg, msglen);
! 		doaxfr(np, rfp, 1);
  		fwritemsg(rfp, msg, msglen);
  		(void) fflush(rfp);
  		exit(0);
--- 1357,1363 ----
  		if (fdstat != -1)
  			(void) fcntl(qsp->s_rfd, F_SETFL, fdstat & ~FNDELAY);
  		fwritemsg(rfp, msg, msglen);
! 		doaxfr(np, rfp, 1, np);
  		fwritemsg(rfp, msg, msglen);
  		(void) fflush(rfp);
  		exit(0);
--
Don "Truck" Lewis                      Harris Semiconductor
Internet:  del@mlb.semi.harris.com     PO Box 883   MS 62A-028
Phone:     (407) 729-5205              Melbourne, FL  32901

asp@UUNET.UU.NET (Andrew Partan) (02/06/91)

I just fixed the other side of the problem - I hacked named-xfer to not
accept any unneeded glue records in any incoming zone transfers.

We were getting all sorts of bad addresses for our nameservers here - we
even had an address of 85.85.85.85 (U.U.U.U) for uunet.

My patch follows.
	--asp@uunet.uu.net (Andrew Partan)

*** named-xfer.c.orig	Wed Aug 15 12:14:48 1990
--- named-xfer.c	Sun Feb  3 21:57:45 1991
***************
*** 883,888 ****
--- 889,896 ----
  	u_char *temp_ptr;	/* used to get ttl for RR */
  	char *cdata, *origin, *proto, dname[MAXDNAME];
  	extern char *inet_ntoa(), *protocolname(), *servicename();
+ 	char * ignore = "";
+ 	int lend, lenn;
  
  	cp = rrp;
  	if ((n = dn_expand(msg, msg + msglen, cp, (u_char *) dname,
***************
*** 1016,1027 ****
  		else
  			got_soa++;
  	}
  	/*
  	 * If the origin has changed, print the new origin
  	 */
  	if (strcasecmp(prev_origin, origin)) {
  		(void) strcpy(prev_origin, origin);
! 		(void) fprintf(dbfp, "$ORIGIN %s.\n", origin);
  	}
  	tab = 0;
  
--- 1024,1068 ----
  		else
  			got_soa++;
  	}
+ 
  	/*
+ 	 * If they are trying to tell us info about something that is
+ 	 * not in the zone that we are transfering, then ignore it!
+ 	 * They don't have the authority to tell us this info.
+ 	 *
+ 	 * We have to do a bit of checking here - the name that we are
+ 	 * checking vs is fully qualified & may be in a subdomain of the
+ 	 * zone in question.  We also need to ignore any final dots.
+ 	 */
+ 	lend = strlen(domain);
+ 	if (domain[lend-1] == '.')
+ 		lend--;
+ 	lenn = strlen(dname);
+ 	if (dname[lenn-1] == '.')
+ 		lenn--;
+ 	if ((lend <= 0) || (lend > lenn) || strncasecmp(domain, &dname[lenn-lend], lend)) {
+ #ifdef not-yet
+ #ifdef DEBUG
+ 		if (debug) {
+ 			(void) fprintf(dbfp, "; Ignoring info about %s, not in zone %s.\n",
+ 				dname, domain);
+ 			ignore = "; ";
+ 		} else
+ #endif
+ 			return result;
+ #else
+ 		(void) fprintf(dbfp, "; Ignoring info about %s, not in zone %s.\n",
+ 			dname, domain);
+ 		ignore = "; ";
+ #endif
+ 	}
+ 
+ 	/*
  	 * If the origin has changed, print the new origin
  	 */
  	if (strcasecmp(prev_origin, origin)) {
  		(void) strcpy(prev_origin, origin);
! 		(void) fprintf(dbfp, "%s$ORIGIN %s.\n", ignore, origin);
  	}
  	tab = 0;
  
***************
*** 1038,1052 ****
  
  		if (dname[0] == 0) {
  			if (origin[0] == 0)
! 				(void) fprintf(dbfp, ".\t");
  			else
! 				(void) fprintf(dbfp, ".%s.\t", origin);	/* ??? */
  		} else
! 			(void) fprintf(dbfp, "%s\t", dname);
  		if (strlen(dname) < 8)
  			tab = 1;
  	} else {
! 		(void) putc('\t', dbfp);
  		tab = 1;
  	}
  
--- 1079,1094 ----
  
  		if (dname[0] == 0) {
  			if (origin[0] == 0)
! 				(void) fprintf(dbfp, "%s.\t", ignore);
  			else
! 				(void) fprintf(dbfp, "%s.%s.\t",
! 					ignore, origin);	/* ??? */
  		} else
! 			(void) fprintf(dbfp, "%s%s\t", ignore, dname);
  		if (strlen(dname) < 8)
  			tab = 1;
  	} else {
! 		(void) fprintf(dbfp, "%s\t", ignore);
  		tab = 1;
  	}
  
***************
*** 1115,1121 ****
  		(void) fprintf(dbfp, " %s. (\n", cp);
  		cp += strlen((char *) cp) + 1;
  		GETLONG(n, cp);
! 		(void) fprintf(dbfp, "\t\t%lu", n);
  		GETLONG(n, cp);
  		(void) fprintf(dbfp, " %lu", n);
  		GETLONG(n, cp);
--- 1157,1163 ----
  		(void) fprintf(dbfp, " %s. (\n", cp);
  		cp += strlen((char *) cp) + 1;
  		GETLONG(n, cp);
! 		(void) fprintf(dbfp, "%s\t\t%lu", ignore, n);
  		GETLONG(n, cp);
  		(void) fprintf(dbfp, " %lu", n);
  		GETLONG(n, cp);

he@spurv.runit.sintef.no (Havard Eidnes) (02/07/91)

In article <1991Feb5.064846.5711@mlb.semi.harris.com>, del@algol.mlb.semi.harris.com (Don Lewis) writes:
|> I made a similar change a while ago.  My patch is a bit fancier
|> (and much more complicated).  It has the advantage that it only includes
|> the extra A records if they reside below an NS RR delegating authority
|> to a subzone.

I think my fix also does that, because doaxfr is called recursively for all
RRs in a zone (or did I read the code wrong?). Anyway, it appears to
exhibit this desireable behaviour with my fix.

- Havard

del@algol.mlb.semi.harris.com (Don Lewis) (02/08/91)

In article <1991Feb6.185422.8886@ugle.unit.no> he@idt.unit.no (Havard Eidnes) writes:
>In article <1991Feb5.064846.5711@mlb.semi.harris.com>, del@algol.mlb.semi.harris.com (Don Lewis) writes:
>|> I made a similar change a while ago.  My patch is a bit fancier
>|> (and much more complicated).  It has the advantage that it only includes
>|> the extra A records if they reside below an NS RR delegating authority
>|> to a subzone.
>
>I think my fix also does that, because doaxfr is called recursively for all
>RRs in a zone (or did I read the code wrong?). Anyway, it appears to
>exhibit this desireable behaviour with my fix.

Your fix only includes the A records if they only if they reside below the
NS.  It does not include the A records if they reside below another NS
in the same zone.  Example zone file for foo.com.

; Stock BIND and your version will include the A RR for hosta in the zone xfer
; (redundant)
		IN	NS	hosta
; Stock BIND may include the A RR for xyzzy.edu
		IN	NS	xyzzy.edu
; All versions include the A RR hosta
hosta		IN	A	x.x.x.x

; All versions include the A RR for host1.sub1
sub1		IN	NS	host1.sub1
; Stock BIND and my version include the A RR for host3.sub2 (required glue)
		IN	NS	host3.sub2
; Outside the zone, not included in the zone xfer
host1.sub1	IN	A	x.x.x.x
; Outside the zone, not included in the zone xfer
host3.sub2	IN	A	x.x.x.x

; Stock BIND and my version include the A RR for hosta (redundant)
sub2		IN	NS	hosta
; Stock BIND and my version include the A RR for host1.sub1 (redundant)
sub1		IN	NS	host1.sub1
; All versions include the A RR for host2.sub2
		IN	NS	host2.sub2
; Outside the zone, not included in the zone xfer
host2.sub2	IN	A	x.x.x.x

The main problem with your version is that it does not transfer the
A RR for host3.sub2.  The only real defect in my version of the fix
is the inclusion of a few redundant A RRs.

Also, if the a host acts as server for both foo.com and sub1.foo.com,
and there is an MX (or other such RR) for sub1.foo.com (which is
part of the sub1.foo.com zone), stock BIND includes this RR in zone
transfers of the foo.com zone.  My patch takes care of this problem as
well.
--
Don "Truck" Lewis                      Harris Semiconductor
Internet:  del@mlb.semi.harris.com     PO Box 883   MS 62A-028
Phone:     (407) 729-5205              Melbourne, FL  32901