#!/bin/bash
# vim: cindent:shiftwidth=4:tabstop=4:smarttab:textwidth=100

set -o errexit
set -o nounset
#set -o xtrace

shopt -s nocasematch

#$title$ DNS zone authority status
#$check$ this server is supposed to be authoritative for defined zone(s); remote (upstream) servers respond and agree
#$ref$ KB:CorrectingDNSAuthority KB:DomainNameSystemSecurityExtensions
#$author$ Rafal Rzeczkowski
#$version$ 0.9.8

level_check long extended

#CHANGELOG
#0.50	initial
#0.51	comprehensive TMP cleanup
#0.52	compare SOA serial numbers across all nameservers
#0.53	skip DNSSEC files
#0.54	skip SOA template files
#0.55	spelling correction in output message
#0.56	set unknown status when no zones have been processed
#0.57	skip subdirectories (test -f not -s)
#0.58	check server version to see if recursion is supported
#0.59	fix ORIGIN to . when querying for NS records
#0.60	enumerate zones using "rndc dumpdb"
#0.61	check that the external and the internal NS set match
#0.62	allow 4 seconds for zones dump, exit plugin if failed
#0.63	DNSSEC RRSIG time validity check (local only)
#0.64	use --utc for date command because DNSSEC uses UTC
#0.65	variable name correction for DNSSEC warning status
#0.66	delegation path trace for Internet zones
#0.67	always examine all zones and return composite status
#0.68	use a non-recursive query to determine nameserver addresses
#0.69	revert 0.68, use a recursive query but not to localhost
#0.70	recognize RT records as placeholders for non-authoritative NS
#0.71	code cleanup, single TMPDIR
#0.72	leave debug output when NS trace awk verification fails
#0.73	correct exclusion patch for known non-authoritative nameservers
#0.74	ignore nonexistent files on TMPDIR cleanup
#0.75	disable case sensitivity for comparing glue data
#0.76	skip SOA checks on fast-updating mta.* zones
#0.77	ignore case when matching blacklisted authoritative nameservers
#0.78	exclusion for the root zone when using absolute notation
#0.79	ignore the root zone completely
#0.80	added metadata headers and help messages
#0.81	full check of glue including nameserver addresses
#0.82	redesigned full check of glue - permit additional (e.g. IPv6) local addresses
#0.83	exclude internal zones from consideration (e.g. RFC6598 in BIND9.9)
#0.84	ignore partial responses when running dig trace to avoid TCP mode transition
#0.85	recognize (and exclude) internal BIND zones via a self-referencing SOA
#0.86	restyle according to https://kb.clearcable.ca/KB/ProgrammingStyleStandards
#0.90	refactor for more robustness on mixed authoritative/recursive server
#0.91	cleanup temporary directory even when errors were found
#0.92	flag with "extended_run_duration" keyword for on_boot skip
#0.93	prevent plugin crash if no zones are present
#0.94	synchronize flag presentation with updated level_check API
#0.95	exclude local TLDs from authoritative analysis
#0.96	exclude RFC1918 zones from authoritative analysis
#0.9.7	exclude RFC4193 zones from authoritative analysis
#0.9.8	skip authority check for locally overridden zones

declare -r RFC1918_REGEXP='^((10)|(172[.]1[6-9])|(172[.]2[0-9])|(172[.]3[0-1])|(192[.]168))[.]'
declare -r RFC1918_REV_REGEXP='(^|[.])(10|168[.]192|(1[6-9]|2[0-9]|3[0-1])[.]172)[.]in-addr[.]arpa[.]$'
declare -r RFC4193_REV_REGEXP='[.][cd][.]f[.]ip6[.]arpa[.]$' # Unique Local IPv6 Unicast Addresses

declare -r DNSSEC_DATE_FORMAT='%Y%m%d%H%M%S'
declare -r DNSSEC_DATE_MARGIN='+5 days'

declare -r DUMP_FILE='/var/cache/bind/named_dump.db'
declare -r DUMP_DELAY=4

check_dig_version () {
	declare -r DIG_VERSION=$(dig -v 2>&1)
	if [[ "$DIG_VERSION" = 'DiG 9.7.3' ]]; then
		DIG_TRACE_WORKAROUND='+ignore +notcp'
		#otherwise +trace fails with DiG 9.7.3 against f.root-servers.net
	else
		DIG_TRACE_WORKAROUND=''
	fi
	echodebug "$DIG_VERSION"
}

select_local_query_address () {
	local -r DISABLE_IPV6=$(sysctl -n net.ipv6.conf.all.disable_ipv6)
	if [[ $DISABLE_IPV6 -gt 0 ]]; then
		LOCALHOST='localhost'
	else
		LOCALHOST='ip6-localhost'
	fi
	echodebug "selected $LOCALHOST as the local DNS query target"
}

delay_fail () {
	local -r LEVEL=$1
	local -r MSG="$2"

	case $LEVEL in
		warning)
			if [[ $ERROR -lt 2 ]]; then
				ERROR=1
				ERROR_MSG="$MSG"
			fi
			;;
		critical)
			ERROR=2
			ERROR_MSG="$MSG"
			;;
	esac
	echodebug "$ERROR_MSG"
	return 0
}

validate_local_nameservers () {
	local -r ZONE=$1

	if ! dig $ZONE. ns @$LOCALHOST > NS_INT; then
		delay_fail warning "internal NS query to $LOCALHOST failed for $ZONE"
		helpmsg 'is the DNS server running?'
		return 2
	fi

	NS_NAMES_INT=($(awk --assign ZONE=$ZONE. '{if ($1==ZONE && $3=="IN" && $4=="NS"){print $5}}' NS_INT))
	if [[ ${#NS_NAMES_INT[*]} -eq 0 ]]; then
		delay_fail warning "no internal NS records found for $ZONE"
		helpmsg 'ensure that the zone file lists two or more valid NS records for the zone'
		return 2
	fi
	NS_NAMES_INT=($(printf '%s\n' "${NS_NAMES_INT[@]}"|sort))
	#echo ${NS_NAMES_INT[*]}

	local NS_NAME
	for NS_NAME in ${NS_NAMES_INT[*]}; do
		NS_NAME=${NS_NAME%.}
		dig +short $NS_NAME A		>> NS_ADDR
		dig +short $NS_NAME AAAA	>> NS_ADDR
		if [[ ! -s NS_ADDR ]]; then
			delay_fail warning "no address found for NS $NS_NAME serving $ZONE"
			helpmsg 'if the NS record is foreign ensure that the name resolves to A and AAAA records in the foreign zone; otherwise check glue definition'
			return 2
		fi
	done

	NS_ADDR_LIST=($(<NS_ADDR))
	if ! grep --quiet --fixed-strings --line-regexp --file=NS_ADDR <<< "$(array_to_lines ${LOCAL_ADDRESSES[*]})"; then
		delay_fail warning "none of the NS addresses ${NS_ADDR_LIST[*]} for zone $ZONE are bound locally"
		helpmsg 'the zone may be registered elsewhere and the zone file should not exist on this server'
		return 2
	fi

	return 0
}


validate_dnssec () {
	local ZONE=$1

	# DNSSEC validation
	# Monitoring DNSSEC zones: what, how and when?
	# http://conferences.npl.co.uk/satin/papers/satin2011-Bortzmeyer.pdf
	if ! dig +cdflag +dnssec +noadditional +noauthority SOA $ZONE @$LOCALHOST > SOA; then
		delay_fail warning "failed to run dig for DNSSEC status of $ZONE"
		helpmsg 'internal plugin error'
		return 2
	fi
	local -a SIGNATURE_TIMES=($(awk '$4 == "RRSIG" { print $10 "\t" $9 }' 'SOA'))

	local -i DATE_CUR=$(date --utc +$DNSSEC_DATE_FORMAT)
	local -i DATE_WARN=$(date --utc --date="$DNSSEC_DATE_MARGIN" +$DNSSEC_DATE_FORMAT)

	if [[ ${#SIGNATURE_TIMES[*]} -eq 0 ]]; then
		# no DNSSEC data
		return 1
	elif [[ $DATE_CUR -lt ${SIGNATURE_TIMES[0]} ]]; then
		delay_fail warning "$ZONE signature is not yet valid"
		helpmsg 'has the time has been reset on this node?'
		return 2
	elif [[ $DATE_CUR -gt ${SIGNATURE_TIMES[1]} ]]; then
		delay_fail critical "$ZONE signature is expired"
		helpmsg 'ensure that dnssec-autosign is working correctly'
		return 2
	elif [[ $DATE_WARN -gt ${SIGNATURE_TIMES[1]} ]]; then
		delay_fail warning "$ZONE signature is about to expire in less than $DNSSEC_DATE_MARGIN"
		helpmsg 'ensure that dnssec-autosign is working correctly'
		return 2
	else
		# DNSSEC RRSIG time validity is OK
		return 0
	fi
}

validate_soa_serial () {
	local ZONE=$1

	SOA_SERIAL=''
	local NS_ADDR
	for NS_ADDR in ${NS_ADDR_LIST[*]}; do
		local NS_VERSION=$(dig +short +norecurse -c CH -t TXT 'version.bind.' @$NS_ADDR)

		if ! dig $ZONE. SOA @$NS_ADDR > SOA; then
			delay_fail warning "NS $NS_ADDR is not responding to SOA query for $ZONE"
			helpmsg 'is the DNS server running and not filtering requests from our IP address?'
			return 2
		fi

		local SOA_SERIAL_CUR=$(awk --assign ZONE=$ZONE. '{if ($1==ZONE && $4=="SOA"){print $7}}' SOA)
		if [[ -z "$SOA_SERIAL_CUR" ]]; then
			delay_fail warning "NS $NS_ADDR returned incorrect response to SOA query for $ZONE"
			helpmsg 'zone file may not be setup correctly'
			return 2
		fi
		if [[ -z "$SOA_SERIAL" ]]; then
			SOA_SERIAL=$SOA_SERIAL_CUR
		elif [[ "$SOA_SERIAL" != "$SOA_SERIAL_CUR" ]]; then
			case $ZONE in
			cpe.*|mta.*)
				#accept SOA divergence for fast updating zones
				SOA_SERIAL='*'
				return 1
				;;
			*)
				delay_fail warning "NS $NS_ADDR returned suspicious SOA serial $SOA_SERIAL_CUR, previously seen $SOA_SERIAL for zone $ZONE"
				helpmsg 'ensure that zone update notifies are being sent and that zone transfers are working in real-time'
				return 2
				;;
			esac
		fi
	done
}

validate_auth_basic () {
	local -r ZONE=$1

	local ZONE_PARENT=$ZONE.
	local -a PARENT_NSES=()

	while [[ ${#PARENT_NSES[*]} -eq 0 ]]; do
		ZONE_PARENT=${ZONE_PARENT#*.}
		if [[ -z "$ZONE_PARENT" ]]; then
			break
		fi
		PARENT_NSES=($(dig +short $ZONE_PARENT ns))
	done

	if [[ ${#PARENT_NSES[*]} -eq 0 ]]; then
		delay_fail warning "no parent nameservers found for $ZONE"
		helpmsg 'unknown exception; please contact the plugin maintainer'
		return 2
	fi

	local -r PARENT_NS1=${PARENT_NSES[0]}
	if ! dig $ZONE. ns @$PARENT_NS1 > NS_EXT; then
		delay_fail warning "external NS query to $PARENT_NS1 failed for $ZONE"
		helpmsg 'check dns-resolver plugin status; is the zone expired?'
		return 2
	fi

	local -a NS_NAMES_EXT=($(awk --assign ZONE=$ZONE. '{if ($1==ZONE && $3=="IN" && $4=="NS"){print $5}}' NS_EXT))
	if [[ ${#NS_NAMES_EXT[*]} -eq 0 ]]; then
		delay_fail warning "no external NS records found for $ZONE"
		helpmsg 'ensure that the delegation chain is working'
		return 2
	fi
	NS_NAMES_EXT=($(printf '%s\n' "${NS_NAMES_EXT[@]}"|tr '[:upper:]' '[:lower:]'|sort))
	#echo ${NS_NAMES_EXT[*]}

	if [[ "${NS_NAMES_INT[*]}" != "${NS_NAMES_EXT[*]}" ]]; then
		delay_fail warning "NS record set mismatch for $ZONE: internal={${NS_NAMES_INT[*]}} external={${NS_NAMES_EXT[*]}}"
		helpmsg 'glue records defined in the delegation chain must match the NS records defined in the zone file'
		return 2
	fi

	return 0
}

validate_auth_extended () {
	local -r ZONE=$1

	if ! dig $DIG_TRACE_WORKAROUND +trace $ZONE. ns > NS_GLUE; then
		delay_fail warning "external NS dig query failed for $ZONE"
		helpmsg 'this is an Internet-accessible zone and its delegation path needs to originate at .'
		return 2
	fi
	tac NS_GLUE > NS_GLUE_REV

	if ! awk --assign ZONE=${ZONE}. '
function exists_in(ns,nses,i)
{
	for (i in nses) {
		if (nses[i] == ns) {
			return 1
		}
	}
	return 0
}
BEGIN { section = 0; i = 0; }
{
#print ">" $0 "<" > "/dev/stderr"
if ($0 ~ /^;; Received [0-9]+ bytes from/) {
	#print "NS:" $6 > "/dev/stderr"
	if (tolower($6) ~ /\(ns5.he.net\)/) {
		delete ns_int
		delete ns_ext
		ns_int[0]="BROKEN - returns non-AUTH response"
		ns_ext[0]="BROKEN - returns non-AUTH response"
		exit
	}
	next
}
if ($0 ~ /^;/) { next }
if (! $0) {
	if (section == 1) {
		#print "section 1>2 switch" > "/dev/stderr"
		section = 2
		i = 0
	} else if (section == 2) {
		#print "section 2 end" > "/dev/stderr"
		exit
	}
	next
}
if ($1 ~ /^.+[.]$/ && $4 == "NS") {
	NS=tolower($5)
	if (section == 0 || section == 1) {
		section = 1
		ns_int[i]=NS
		#print "ns_int:" NS > "/dev/stderr"
		i++
	}
	if (section == 2) {
		ns_ext[i]=NS
		#print "ns_ext:" NS > "/dev/stderr"
		i++
	}
}
}
END {
	#print length(ns_int)
	#for (i in ns_int){print "int:"ns_int[i]}
	#print length(ns_ext)
	#for (i in ns_ext){print "ext:"ns_ext[i]}
	if (!length(ns_ext)){
		print "0 external nameservers found for " ZONE
		exit 1
	}
	if (!length(ns_int)){
		print "0 internal nameservers found in the " ZONE " zone"
		exit 1
	}

	for (i in ns_int){
		if (!exists_in(ns_int[i],ns_ext)) {
			print "nameserver " ns_int[i] " was found in the " ZONE " zone but not in its glue record"
			exit 1
		}
	}
	for (i in ns_ext){
		if (!exists_in(ns_ext[i],ns_int)) {
			print "nameserver " ns_ext[i] " was found in the glue record but not in the " ZONE " zone"
			exit 1
		}
	}
}' NS_GLUE_REV > NS_GLUE_AWK
	then
		delay_fail warning "$(<NS_GLUE_AWK)"
		helpmsg 'delegation chain analysis has found an error'
		return 2
	fi

	NS_GLUE_HOP_ADDR_LIST=($(dig $DIG_TRACE_WORKAROUND +norecurse +trace $ZONE. NS |
		tac |
		awk '{if ($1$2==";;Received"){split($6,hostspec,"#");print hostspec[1]}}'))
	#echo ${NS_GLUE_HOP_ADDR_LIST[*]}

	for HOP in 0 1; do
		dig +norecurse $ZONE. NS @${NS_GLUE_HOP_ADDR_LIST[$HOP]} >> NS_GLUE_FULL
	done

	if ! awk --assign ZONE_ABS=$ZONE. '
{
	if ($1$2$3==";;ADDITIONALSECTION:") { additional_section++; nameserver_distance++ }
	else if ($1=="") { additional_section="" }
	else if (additional_section) {
		split($1,hostname_fqdn,ZONE_ABS)
		if ($1 == hostname_fqdn[1] ZONE_ABS) {	# circular
			name_and_type=$1 "/" $4
			address=$5
			if (nameserver_distance == 1) {
				addresses[name_and_type]=address
			}
			else if (nameserver_distance == 2) {
				if (! addresses[name_and_type]) {
					print "address for " name_and_type " is missing local definition"
					exit 1
				}
				else if (address != addresses[name_and_type]) {
					print "glue address for " name_and_type " is " address " instead of " addresses[name_and_type] " defined locally"
					exit 1
				}
			}
		}
	}
}' NS_GLUE_FULL > NS_GLUE_FULL_AWK
	then
		delay_fail warning "$(<NS_GLUE_FULL_AWK)"
		helpmsg 'delegation chain analysis has found an error'
		return 2
	fi

	return 0
}

enumerate_local_addresses () {
	# from "ipaddress-dns" plugin
	IPV4_A=$(
	ip -oneline -family inet address show|
	awk '{if($4!~/^127[.]/){gsub("/[0-9]+","",$4);print $4}}')

	IPV6_A=$(
	ip -oneline -family inet6 address show|
	awk '{if($4!~/^(fe80)?::/){gsub("/[0-9]+","",$4);print $4}}')

	LOCAL_ADDRESSES=($IPV4_A $IPV6_A)

	return 0
}

array_to_lines () {
	for ITEM in "$@"; do
		echo "$ITEM"
	done
}

enumerate_zones_raw () {
	rndc dumpdb -zones
	echodebug "initiating dump of all zone names ($DUMP_DELAY second delay)"
	sleep $DUMP_DELAY

	if [[ -s $DUMP_FILE ]]; then
		DATE_DUMP=$(stat --format='%Y' $DUMP_FILE)
	else
		DATE_DUMP=0
	fi

	local DATE_CUR=$(date +'%s')
	if [[ $DATE_DUMP -lt $(( DATE_CUR - 60 )) ]]; then
		fail warning "failed to dump zone list into $DUMP_FILE"
		helpmsg 'internal plugin error'
		exit 1
	fi

	ZONES_RAW=($(awk -F\' '
{
	if ($1==";")
		next
	else if (candidate) {
		split(candidate, zone_spec, "/")
		split($0, line, " ")
		if ($1=="; not implemented") {
			# autogenerated placeholder zone
		}
		else if (zone_spec[1]"."==line[1] && "SOA"==line[4] && zone_spec[1]"."==line[5]) {
			# autogenerated placeholder zone with real child zone(s)
		}
		else
			print candidate
		candidate=""
	}
	else if ($1=="; Zone dump of ")
		candidate=$2
}' $DUMP_FILE))
	#ZONES_RAW=('clearcable.ca/IN')
	#ZONES_RAW=('255.160.192.in-addr.arpa/IN')
	#ZONES_RAW=('localnet/IN')
	echodebug "${#ZONES_RAW[*]} zone(s) found in BIND dump"

	return 0
}

enumerate_zones () {
	ZONES=()
	local ZONE_SPEC
	if [[ ${#ZONES_RAW[*]} -lt 1 ]]; then
		return 0
	fi
	for ZONE_SPEC in ${ZONES_RAW[*]}; do
		local CLASS=${ZONE_SPEC##*/}
		local ZONE=${ZONE_SPEC%%/*}
		case $CLASS in
			CH)continue;; #internal data
		esac
		case $ZONE in
			#Locally Served DNS Zones
			#http://www.iana.org/assignments/locally-served-dns-zones/locally-served-dns-zones.xml
			0.IN-ADDR.ARPA)continue;;		# IPv4 "THIS" NETWORK
			127.IN-ADDR.ARPA)continue;;	# IPv4 LOOPBACK NETWORK
			254.169.IN-ADDR.ARPA)continue;;	# IPv4 LINK LOCAL
			2.0.192.IN-ADDR.ARPA)continue;;	# IPv4 TEST-NET-1
			100.51.198.IN-ADDR.ARPA)continue;;	# IPv4 TEST-NET-2
			113.0.203.IN-ADDR.ARPA)continue;;		# IPv4 TEST-NET-3
			255.255.255.255.IN-ADDR.ARPA)continue;;	# IPv4 BROADCAST
			0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.IP6.ARPA)continue;;	# IPv6 Unspecified Address
			1.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.IP6.ARPA)continue;;	# IPv6 Loopback Address
			D.F.IP6.ARPA)continue;;	# IPv6 Locally Assigned Local Address
			8.E.F.IP6.ARPA)continue;;	# IPv6 Link Local Address
			9.E.F.IP6.ARPA)continue;;	# IPv6 Link Local Address
			A.E.F.IP6.ARPA)continue;;	# IPv6 Link Local Address
			B.E.F.IP6.ARPA)continue;;	# IPv6 Link Local Address
			8.B.D.0.1.0.0.2.IP6.ARPA)continue;;	# IPv6 Example Prefix
			[.])continue;;	# root
			list)continue;;
		esac
		ZONES[$((${#ZONES[*]}+1))]=$ZONE
	done
	echodebug "${#ZONES[*]} zone(s) left after filtering"

	return 0
}

check_dig_version
select_local_query_address
enumerate_local_addresses
enumerate_zones_raw
enumerate_zones

ERROR=0
TMPDIR_PER_ZONE=''
if [[ ${#ZONES[*]} -gt 0 ]]; then
	for ZONE in ${ZONES[*]}; do
		[ -d "$TMPDIR_PER_ZONE" ] && rm --recursive $TMPDIR_PER_ZONE
		TMPDIR_PER_ZONE=$(mktemp --directory)
		cd $TMPDIR_PER_ZONE
	
		echo $ZONE > ZONE
	
		if validate_local_nameservers $ZONE; then
			:
		else
			if [[ $? -gt 1 ]]; then
				continue
			fi
		fi

		ns_names_int_flat=${NS_NAMES_INT[@]}
		if grep --quiet --extended-regexp "$RFC1918_REGEXP" NS_ADDR; then
			# RFC1918 NS address(es)
			AUTH_STATUS=''
			DNSSEC_STATUS=''
		elif [[ "$ZONE." =~ $RFC1918_REV_REGEXP || "$ZONE." =~ $RFC4193_REV_REGEXP ]]; then
			# RFC1918/RFC4193 reverse zone
			AUTH_STATUS=''
			DNSSEC_STATUS=''
		elif [[ "$ZONE" =~ ^[[:alnum:]]+$ ]]; then
			# local TLD, e.g. "localnet"
			AUTH_STATUS=''
			DNSSEC_STATUS=''
		elif [[ "$ns_names_int_flat" = "${ns_names_int_flat#*[a-z]}" ]]; then
			# local override of an authoritative zone
			AUTH_STATUS=''
			DNSSEC_STATUS=''
		else
			# Internet accessible namespace
			AUTH_STATUS='+AUTH'
		fi
	
		if [[ -n "$AUTH_STATUS" ]]; then
			if validate_auth_basic $ZONE; then
				:
			else
				if [[ $? -gt 1 ]]; then
					continue
				fi
			fi
	
			if validate_auth_extended $ZONE; then
				:
			else
				if [[ $? -gt 1 ]]; then
					continue
				fi
			fi
	
			if validate_dnssec $ZONE; then
				DNSSEC_STATUS='+DNSSEC'
			else
				if [[ $? -gt 1 ]]; then
					continue
				fi
				DNSSEC_STATUS=''
			fi
		fi
	
		if validate_soa_serial $ZONE; then
			:
		else
			if [[ $? -gt 1 ]]; then
				continue
			fi
		fi
	
		echodebug "zone ${#NS_NAMES_INT[*]}(${#NS_ADDR_LIST[*]})NS $SOA_SERIAL $ZONE [OK$AUTH_STATUS$DNSSEC_STATUS]"
	done

	[ -d "$TMPDIR_PER_ZONE" ] && rm --recursive $TMPDIR_PER_ZONE

	if [[ $ERROR -eq 0 ]]; then
		ok
	elif [[ $ERROR -eq 1 ]]; then
		fail warning "$ERROR_MSG"
	elif [[ $ERROR -eq 2 ]]; then
		fail critical "$ERROR_MSG"
	else
		exit 2
	fi
else
	unknown
fi

# *REFERENCE*

# [dig - QUERY OPTIONS]

# +[no]cdflag Set [do not set] the CD (checking disabled) bit in the query.
# This requests the server to not perform DNSSEC validation of responses.

# +[no]dnssec
# Requests DNSSEC records be sent by setting the DNSSEC OK bit (DO)
# in the OPT record in the additional section of the query.

# +[no]additional
# Display [do not display] the additional section of a reply.
# The default is to display it.

# +[no]authority
# Display [do not display] the authority section of a reply.
# The default is to display it.

#RRSIG-records have the following data elements:
#- Type Covered: DNS record type that this signature covers.
#- Algorithm: Cryptographic algorithm used to create the signature.
#- Labels: Number of labels in the original RRSIG-record name (used to validate wildcards).
#- Original TTL: TTL value of the covered record set.
#- Signature Expiration: When the signature expires.
#- Signature Inception: When the signature was created.
#- Key Tag: A short numeric value which can help quickly identify the DNSKEY-record which can be used to validate this signature.
#- Signer's Name: Name of the DNSKEY-record which can be used to validate this signature.
#- Signature: Cryptographic signature.
