#! /bin/sh
#
#ident "@(#)$Id: gtld-wildhosts.sh,v 1.5 2013/09/27 23:50:11 woods Exp $"

# This script will try a random name for each gTLD to reveal whether
# there's a wildcard for that zone, and then, prints the A RR value(s)
# for the host pointed to by that wildcard record.
#
# Up to date versions of this script can be found here:
#
#	ftp://ftp.weird.com/pub/local/gtld-wildhosts.sh
#	http://www.weird.com/~woods/projects/gtld-wildhosts.sh
#
# The addresses discovered by this script can be used with Unbound's
# "private-addresses" option.
#
# BIND-9.7 (and newer) users can add these addresses to the
# "deny-answer-addresses" list.
#
# NOTE: BIND-9 users can also turn on "root-delegation-only" (or use
# the "delegation-only" option per zone) and with this feature enabled
# the results of this script are not needed.  Take care to understand
# the difference between forcing TLDs to be "delegation-only" vs. just
# filtering out addresses of known wildcard target hosts.
#
# Patches adding a similar feature to BIND-8 can be found here:
#
#	ftp://ftp.weird.com/pub/local/bind-8.4.7-REL-Planix-1.diff
#
# An ACL definition would then be configured:
#
#	# Deny A records with these target addresses.  This prevents stupid
#	# wildcard A RRs in delegation-only zones from having any effect.
#	#
#	# Note that .MUSEUM is supposed to have a wildcard -- it's defined
#	# to work that way.
#	#
#	acl deny_address_record_acl {
#		64.74.134.14;	# .CD.
#		66.113.70.101;	# .CD.	(target of their wildcard MX)
#	. . .
#	#	195.7.77.20;	# .MUSEUM.
#	. . .
#	#	127.0.0.2;	# .SO.	(randomly comes and goes)
#	. . .
#	};
#
# and then in the "options" section:
#
#	options {
#	. . .
#		# Deny bogus A RRs (turn them into NXDOMAIN responses)
#		#
#		# WARNING: to see rejected records named must have its
#		# debugging level increased, e.g. by sending it a SIGUSR1.
#		#
#		deny_address_record {
#			deny_address_record_acl;
#		};
#
# For a complete example see the named.conf file included in this:
#
#	ftp://ftp.weird.com/pub/local/named-sample-conf.src.shar
#
# TODO:
#
# - handle IPv6 addresses too.
#
# - find targets wildcard top-level MX records like the one for .CD
#
# NOTE: the "ftp" command must be one supporting '-o -' and URL
# formatted parameters
#
# NOTE: the "host" command must be one supporting the "--canonskip"
# parameter; as well as "-t RTYPE", "-r", and "-Z"

# get the canonical list of all gTLDs
#
# each entry must terminate in a "."
#
all_tlds=$(ftp -o - ftp://ftp.internic.net/domain/root.zone.gz | gunzip -c | awk '$1 != "." && $4 == "NS" {print $1}' | sort -u)

# find the IP address of a root server we can talk to
#
# we'll get the NS records for each TLD directly from this server
#
root_ns=$(host --canonskip -t NS . | awk '$2 == "NS" {print $NF; exit 0;}')
root_a=$(host --canonskip -t A $root_ns | awk '$2 == "A" {print $NF; exit 0;}')

host -r --canonskip -t SOA . $root_a > /dev/null
if [ $? -ne 0 ]; then
	echo "There seems to be something wrong with the root NS at $root_a" >&2
	exit 1
fi

for tld in $all_tlds; do

	nslist=$(host --canonskip -t NS $tld $root_a 2> /dev/null | awk '$2 == "NS" {print $NF;}')

	# we probably don't have to check them all, but since it is a
	# little difficult without additional parsing to tell whether
	# the reason for a query failure is an authoritative NXDOMAIN
	# or not, we'll check all auth nameservers stopping early only
	# if we actually do find a wildcard record...
	#
	for auth_ns in $nslist; do

		cname=$(host -r --canonskip -t CNAME "*.${tld}" $auth_ns 2> /dev/null | awk '$2 == "CNAME" {print $3}')

		case "${cname}" in
		#
		# if there's no CNAME for "*.gtld" then we ask an A RR
		#
		"")
			host -r -Z --canonskip -t A "*.${tld}" $auth_ns 2> /dev/null | awk '$4 == "A" {print $5 ";\t# .'$tld' TTL=" $2;}'
			;;
		#
		# in the case of a gTLD having a CNAME for "*.gtld"
		# then we need to find the A RR for this CNAME the
		# parent domain of the CNAME target.
		#
		# note: don't use '-r' with '-P' on older versions
		#
		*.*)
			host -Z --canonskip -t A -P ${cname} 2> /dev/null | awk '$4 == "A" {print $NF ";\t# .'$tld' (CNAME='${cname}') TTL=" $2;}'
			;;
		*)
			host -Z --canonskip -t A -P ${cname}. 2> /dev/null | awk '$4 == "A" {print $NF ";\t# .'$tld' (CNAME='${cname}') TTL=" $2;}'
			;;
		esac

		if [ $? -eq 0 ]; then
			break;
		fi
	done
done
