#!/bin/sh # SPDX-License-Identifier: GPL-3.0 # # freifunk-franken dns-scipts (c) 2021 Blackyfff GetZoneFileSerial() { if [ -f "$1" ]; then INSOASpec="^\s*\S\+\s\+\([0-9]*\s\)\?\s*IN\s\+SOA\s\+" FirstSOALineAndFollowing="/""$INSOASpec""/,\$!d;" RemoveComments=":a;s/;.*$//g;" RemoveLineBreaks=":a;N;\$!ba;s/\n//g;" SearchPrintSerial="s/""$INSOASpec""\S\+\s\+\S\+\s\+\((\s\)\?\s*\([0-9]*\).*/\3/i" ZoneSerial=$(sed -e "$FirstSOALineAndFollowing""$RemoveComments""$RemoveLineBreaks""$SearchPrintSerial" "$1") fi echo "${ZoneSerial:-0}" } InsertZoneToViews() { Views="$1" ZoneFilesFolder="$2" Domain="$3" SourceFile="$4" TempFolder="$5" DNSSECPolicy="$6" for View in $Views; do ZoneFile="$ZoneFilesFolder""db.""$View"".""$Domain" [ -f "$ZoneFile" ] || ln -s "$SourceFile" "$ZoneFile" InsertZoneToIncludeFile "$Domain" "$ZoneFile" "$TempFolder""$View"".conf" "$DNSSECPolicy" done } InsertZoneToIncludeFile() { if [ ! -f "$3" ]; then { echo "zone \"""$1""\" {" echo " type master;" [ -n "$4" ] && echo " dnssec-policy $4"";" echo " file \"""$2""\";" echo "};" } > "$3" else [ -n "$4" ] && Extra=" dnssec-policy $4"";\n" || Extra="" sed -i "1i\ zone \"""$1""\" {\n\ type master;\n""$Extra\ file \"""$2""\";\n\ };" "$3" fi } GetAllNameservers() { sed -ne 's/^\s*'"$2"'\s\+\([0-9]*\s\)\?\s*[Ii][Nn]\s\+[Nn][Ss]\s\+\(\S\+\)/\3/p' "$3" | \ sed -e 's/\([^.]\)$/\1\.'"$1"'\./g;s/\.$//g' | \ awk '!a[$0]++' } GetAllSubNameservers() { Domain="$(SEDifyHostname "$1")" SubDomain="$(SEDifyHostname "$2")" GetAllNameservers "$1" "$SubDomain""\(\.""$Domain""\.\)\?" "$3" } GetAllZoneNameservers() { Domain="""$(SEDifyHostname "$1")" GetAllNameservers "$1" "\(@\|""$Domain""\.\)" "$2" } GetReverseZoneFileFromZone() { echo "db.""$(echo "$1" | awk -F. '{ printf $(NF-2);for(i=NF-3;i>0;--i) printf "."$i}')" } FillIPv4MissingBlocks() { echo "$1" | sed -ne 's/^\([^.]\+\)\.\(\([^.]\+\)\.\)\?\(\([^.]\+\)\.\)\?\([^.]\+\)$/\1.\3.\5.\6/p' | sed -r 's/\.\./\.0\./g;s/\.\./\.0\./g' } GetReverseIPv4Domains() { IPFilled="$(FillIPv4MissingBlocks "${1%/*}")" Mask="${1##*/}" Statics=$((Mask / 8)) Filler=$((Mask % 8)) RevDomain="$(echo "$IPFilled" | awk -F. '{for(i='"$Statics"';i>0;--i)printf "."$i}')"".in-addr.arpa." if [ $Filler -eq 0 ]; then echo "${RevDomain#.}" else Filler=$((8 - Filler)) Filler=$((1 << $Filler)) Start=$(echo "$IPFilled" | awk -F. '{printf $'"$((Statics+1))"'}') Start=$((Start - Start % Filler)) for Sub in $(seq $Start $((Start + Filler - 1))); do echo "$Sub""$RevDomain" done fi } FillIPv6Zeroes() { echo "$1" | awk -F: 'BEGIN{OFS=""}{FillCount=9-NF; for(i=1;i<=NF;i++){if(length($i)!=0||i==1||i==NF) {$i=substr(("0000" $i), length($i)+1);} else {for(j=1;j<=FillCount;j++){$i=($i "0000");}}}; print}' } GetReverseIPv6Domains() { IPFilled="$(FillIPv6Zeroes "$(echo "${1%/*}" | awk '{print tolower($0)}')")" Mask="${1##*/}" Statics=$((Mask / 4)) Filler=$((Mask % 4)) RevDomain="$(echo "$IPFilled" | awk 'BEGIN{FS=""}{for(i='"$Statics"';i>0;i--) printf "." $i;}')"".ip6.arpa." if [ $Filler -eq 0 ]; then echo "${RevDomain#.}" else Filler=$((4 - Filler)) Filler=$((1 << $Filler)) Start="$(printf %d 0x"$(echo "$IPFilled" | awk 'BEGIN{FS=""}{printf $'"$((Statics+1))"'}')")" Start=$((Start - Start % Filler)) for Sub in $(seq $Start $((Start + Filler - 1))); do echo "$(printf %x "$Sub")""$RevDomain" done fi } GetReverseDomains() { Subnet="$1" if IsValidIPv4Subnet "$Subnet"; then GetReverseIPv4Domains "$Subnet" elif IsValidIPv6Subnet "$Subnet"; then GetReverseIPv6Domains "$Subnet" else TraceErrAndExit "$1"" is no valid Subnet" fi } ExpandHostname() { Hostname="$1" if [ "$Hostname" = "@" ]; then Hostname="$2" else [ -n "${Hostname##*.}" ] && Hostname="$Hostname"".""$2" fi echo "$Hostname" } SEDifyHostname() { echo "$1" | sed -r 's/\./\\\./g' } GetServernameSEDEntry() { CommunityName="$1" ServerName="$DNSSCRIPT_SERVER_NAME" if [ -z "${ServerName##*$CommunityName}" ]; then ServerName="\(""$ServerName"".\|""${ServerName%*.$CommunityName}""\)" else ServerName="\(""$ServerName"".\)" fi SEDifyHostname "$ServerName" } NormalizeZoneFileFormatting() { awk 'BEGIN{FS="\t"}{l=length($1);f=substr(" ", 1+length($1)); s=substr(" ", 1+length($2)); x=substr($0,length($1)+length($2)+3); print $1 f " " $2 s " " x}' } GetOwnGlueRecords() { ServerName="$DNSSCRIPT_SERVER_NAME" if [ -z "${ServerName##*$2}" ]; then ServerName="${ServerName%.$2}" sed -ne 's/^\s*'"$(GetServernameSEDEntry "$1")"'\s\+[Ii][Nn]\s\+\([Aa]\|[Aa]\{4\}\)\s\+\(.*\)$/'"$ServerName"'\tIN \2\t\3/p' "$3" | \ NormalizeZoneFileFormatting fi } GetOwnHoods() { Entries="$(sed -ne "s/^\s*\(\S*\).*\s\+[Ii][Nn]\s\+[Nn][Ss]\s\+""$(GetServernameSEDEntry "$1")""\s*;\s*Subnets:\s*\([^;]*\)/\1 \3/p" "$2")" Entries="$(echo "$Entries" | sed -e '/^[eE][xX][tT][eE][rR][nN]\s/d' | sed -r 's/\s+/#/g')" echo "$Entries" } IsValidIPv4Subnet() { [ -n "$(echo "$1" | sed -e '/[^/]*\/\([12]\?[0-9]\|3[0-2]\)$/!d')" ] && IsValidIPv4 "${1%/*}" return $? } IsValidIPv4() { [ -n "$(echo "$1" | sed -e '/^\(\(25[0-5]\|\(2[0-4]\|1[0-9]\|[1-9]\)\?[0-9]\)\.\)\{0,3\}\(25[0-5]\|\(2[0-4]\|1[0-9]\|[1-9]\)\?[0-9]\)$/!d')" ] return $? } IsValidIPv6Subnet() { [ -n "$(echo "$1" | sed -e '/[^/]*\/\([1-9]\?[0-9]\|1\([01][0-9]\|2[0-8]\)\)$/!d')" ] && IsValidIPv6 "${1%/*}" return $? } IsValidIPv6() { Max8BlocksMax4Hex="/^\([0-9a-fA-F]\{0,4\}[:]\{1,2\}\)\{1,7\}[0-9a-fA-F]\{0,4\}$/!d;" MaxOneDoubleColon="/^.*::.*::.*$/d;" SingleColon8BlocksOrNoSingleColonBeginEnd="/^\(\([^:]\+:\)\{7\}[^:]\+\|\(\|[^:].*\)::\(\|.*[^:]\)\)$/!d" [ -n "$(echo "$1" | sed -e "$Max8BlocksMax4Hex""$MaxOneDoubleColon""$SingleColon8BlocksOrNoSingleColonBeginEnd")" ] return $? } IPv4IsInSubnet() { IPFilled="$(FillIPv4MissingBlocks "$1")" SubnetIPFilled="$(FillIPv4MissingBlocks "${2%/*}")" Mask="${2##*/}" Statics=$((Mask / 8)) BlockMask=$((Mask % 8)) IPStaticPart="$(echo "$IPFilled" | awk -F. '{for(i='"$Statics"';i>0;--i)printf "."$i}')" SubnetStaticPart="$(echo "$SubnetIPFilled" | awk -F. '{for(i='"$Statics"';i>0;--i) printf "."$i}')" AreEqual="$([ "$IPStaticPart" = "$SubnetStaticPart" ]; echo "$?")" if [ $AreEqual -eq 0 ] && [ $BlockMask -ne 0 ]; then BlockMask=$((8 - BlockMask)) BlockMask=$((-1 << $BlockMask)) IPBlock=$(echo "$IPFilled" | awk -F. '{printf $'"$((Statics+1))"'}') SubnetBlock=$(echo "$SubnetIPFilled" | awk -F. '{printf $'"$((Statics+1))"'}') IPBlock=$((IPBlock & BlockMask)) SubnetBlock=$((SubnetBlock & BlockMask)) AreEqual="$([ $IPBlock -eq $SubnetBlock ]; echo "$?")" fi return $AreEqual } GetOwnKeysForZone () { DNSSECKeyFolder="$1" Domain="$2" if [ -n "$DNSSECKeyFolder" ];then for OwnKeyFile in "$DNSSECKeyFolder""K""$Domain"".+"*".key"; do Removed="$(sed -ne 's/^; Delete: \(\S\{12\}\).*/\1/p' "$OwnKeyFile")" if [ -n "$Removed" ]; then Removed="$(date -u -d "$Removed" '+%s')" CurDate="$(date -u '+%s')" if [ $((CurDate - Removed)) -le 172800 ]; then Removed="" fi fi if [ -z "$Removed" ]; then sed -ne '/^;/d;s/^'"$Domain"'\.\s\+\([0-9]*\s\)\?\s*[Ii][Nn]\s\+[Dd][Nn][Ss][Kk][Ee][Yy]\s\+\(.*\)$/_dnsseckeys\.'"$Domain"'\.\tIN TXT\t\"\2\"/p' "$OwnKeyFile" | \ NormalizeZoneFileFormatting fi done fi } UpdateDNSSECEntryCache () { Domain="$1" ZoneTempFolder="$2" CachedZoneFile="$3" DNSSECKeyFolder="$4" UpstreamIP="$5" UpdateMaster=0 [ -z "$UpstreamIP" ] || UpstreamIP="-b ""$UpstreamIP"" " Nameservers="$(GetAllZoneNameservers "$Domain" "$CachedZoneFile")" mkdir -p "$ZoneTempFolder" for KeyFile in "$ZoneTempFolder"*; do [ "$KeyFile" = "$ZoneTempFolder""*" ] || \ mv "$KeyFile" "$ZoneTempFolder""Old""${KeyFile##*$ZoneTempFolder}" done if [ -n "$DNSSECKeyFolder" ]; then for Nameserver in $Nameservers; do if [ "$Nameserver" = "$DNSSCRIPT_SERVER_NAME" ]; then DNSKEYS="$( GetOwnKeysForZone "$DNSSECKeyFolder" "$Domain" )" else DNSKEYS="$(delv @"$Nameserver" "$UpstreamIP"_dnsseckeys."$Domain" TXT 2>/dev/null | \ sed -ne '/^;/d;s/^.*\sIN\s\+TXT\s\+"\(.*\)"$/'"$Domain"'.\tIN DNSKEY\t\1/p' | \ NormalizeZoneFileFormatting )" fi if [ -n "$DNSKEYS" ] && [ "$DNSKEYS" != "$(cat "$ZoneTempFolder""OldKeys.""$Nameserver" 2>/dev/null)" ]; then echo "$DNSKEYS" > "$ZoneTempFolder""Keys.""$Nameserver" UpdateMaster=1 elif [ -f "$ZoneTempFolder""OldKeys.""$Nameserver" ]; then mv "$ZoneTempFolder""OldKeys.""$Nameserver" "$ZoneTempFolder""Keys.""$Nameserver" fi done fi SEDDomain="$(SEDifyHostname "$Domain")" ChildServers="$( sed -ne '/^\s*\(@\|'"$SEDDomain"'\.\)\s/!s/^\s*\(\S\+\)\s\+\([0-9]*\s\)\?\s*[Ii][Nn]\s\+[Nn][Ss]\s\+\(\S\+\);\?.*$/\1#\3/p' "$CachedZoneFile" | \ sed -e 's/\([^.]\)$/\1\.'"$Domain"'\./g;s/\.$//g;s/\([^.]\)#/\1\.'"$Domain"'\.#/g;s/\.#/#/g' )" for ChildServer in $ChildServers; do DNSKEYS="$(delv @"${ChildServer##*\#}" "$UpstreamIP""${ChildServer%%\#*}" CDS 2>/dev/null | \ sed -ne '/^;/d;s/^.*\sIN\s\+CDS\s\+\(.*\)$/'"${ChildServer%%\#*}"'.\tIN DS\t\1/p' | \ NormalizeZoneFileFormatting )" if [ -n "$DNSKEYS" ]; then DNSKEYS="$(echo "$DNSKEYS" | sed -e '/\sIN\s\+DS\s\+0\s\+0\s\+0\s\+0/d')" if [ "$DNSKEYS" != "$(cat "$ZoneTempFolder""OldChildKeys.""$ChildServer" 2>/dev/null)" ]; then [ -z "$DNSKEYS" ] || echo "$DNSKEYS" > "$ZoneTempFolder""ChildKeys.""$ChildServer" UpdateMaster=1 elif [ -n "$DNSKEYS" ]; then mv "$ZoneTempFolder""OldChildKeys.""$ChildServer" "$ZoneTempFolder""ChildKeys.""$ChildServer" elif [ -f "$ZoneTempFolder""OldKeys.""$Nameserver" ]; then UpdateMaster=1 fi elif [ -f "$ZoneTempFolder""OldChildKeys.""$Nameserver" ]; then mv "$ZoneTempFolder""OldChildKeys.""$ChildServer" "$ZoneTempFolder""ChildKeys.""$ChildServer" fi done for KeyFile in "$ZoneTempFolder""Old"*; do [ "$KeyFile" = "$ZoneTempFolder""Old*" ] || \ rm -f "$KeyFile" done echo "$UpdateMaster" } ReloadZone() { if [ -n "$2" ]; then if [ $((DNSSCRIPT_BIND_RELOAD_VER)) -eq 0 ]; then systemctl reload bind9 >/dev/null elif [ $((DNSSCRIPT_BIND_RELOAD_VER)) -eq 1 ]; then for Zone in $2; do if ! rndc reload "$1" IN "$Zone" 2>"/tmp/dnsscript_rndcerr" >/dev/null; then if [ -n "$3" ] && grep -q "failed: out of range" "/tmp/dnsscript_rndcerr"; then rndc sync -clean "$1" IN "$Zone" >/dev/null || touch "/tmp/dnsscript-forcereconf" else touch "/tmp/dnsscript-forcereconf" fi fi rm -f "/tmp/dnsscript_rndcerr" done elif [ $((DNSSCRIPT_BIND_RELOAD_VER)) -eq 2 ]; then /etc/init.d/named reload >/dev/null fi fi } TraceErrAndExit() { echo "$1" 1>&2 exit 1 }