dns-scripts/usr/lib/ffdns/dns-functions.sh

431 lines
15 KiB
Bash
Executable File

#!/bin/sh
# SPDX-License-Identifier: GPL-3.0
#
# freifunk-franken dns-scipts (c) 2021-2023 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"
AdditionalZoneConfig="$5"
for View in $Views; do
ZoneFile="$ZoneFilesFolder""db.""$View"".""$Domain"
[ -f "$ZoneFile" ] || ln -s "$SourceFile" "$ZoneFile"
if [ -n "$DNSSCRIPT_DNSSECPolicy" ]; then
if [ ! -f "$ZoneFilesFolder""db.""$View"".""$Domain"".signed" ]; then
cp -f "$ZoneFile" "$ZoneFilesFolder""db.""$View"".""$Domain"".signed"
fi
ZoneFile="$ZoneFilesFolder""db.""$View"".""$Domain"".signed"
fi
InsertZoneToIncludeFile "$Domain" "$ZoneFile" "$DNSSCRIPT_TEMP_FOLDER""$View"".conf" "$DNSSCRIPT_DNSSECPolicy" "$AdditionalZoneConfig""$View""."
done
}
InsertZoneToIncludeFile() {
if [ -n "$5" ] && [ -f "$5""$1" ]; then
Additional="$(cat "$5""$1")"
else
Additional=""
fi
if [ ! -f "$3" ]; then
{
echo "zone \"""$1""\" {"
echo " type master;"
if [ -n "$4" ]; then
echo " dnssec-policy $4"";"
echo " update-policy {"
echo " grant local subdomain ""$1"". any;"
[ -n "$Additional" ] && echo "$Additional"
echo " };"
fi
echo " file \"""$2""\";"
echo "};"
} > "$3"
else
{
echo "zone \"""$1""\" {"
echo " type master;"
if [ -n "$4" ]; then
echo " dnssec-policy $4"";"
echo " update-policy {"
echo " grant local subdomain ""$1"". any;"
[ -n "$Additional" ] && echo "$Additional"
echo " };"
fi
echo " file \"""$2""\";"
echo "};"
} >> "$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
if ! [ "$OwnKeyFile" = "$DNSSECKeyFolder""K""$Domain"".+*.key" ]; then
Removed="$(sed -ne 's/^; Inactive: \(\S\{12\}\).*/\1/p' "$OwnKeyFile")"
if [ -n "$Removed" ]; then
RemovedISO="$( echo "$Removed" | sed -ne 's/\(.\{4\}\)\(.\{2\}\)\(.\{2\}\)\(.\{2\}\)\(.\{2\}\).*/\1-\2-\3T\4:\5/p')"
RemovedSeconds="$(date -d "$RemovedISO" '+%s' 2>/dev/null)"
[ -n "$RemovedSeconds" ] || RemovedSeconds="$(date -u -d "$Removed" '+%s' 2>/dev/null)"
if [ -n "$RemovedSeconds" ]; then
CurDate="$(date '+%s')"
if [ $((RemovedSeconds)) -ge $((CurDate)) ]; then
RemovedSeconds=""
fi
fi
else
RemovedSeconds=""
fi
if [ -z "$RemovedSeconds" ]; 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
fi
done
fi
}
UpdateDNSSECEntryCache () {
Domain="$1"
ZoneTempFolder="$2"
CachedZoneFile="$3"
DNSSECKeyFolder="$4"
UpstreamIP="$5"
[ -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
{
GetOwnKeysForZone "$DNSSECKeyFolder" "$Domain" | sort
GetDSForZone "$DNSSECKeyFolder" "$Domain" "$DNSSCRIPT_TEMP_FOLDER" | NormalizeZoneFileFormatting
} > "$ZoneTempFolder""Keys.""$Nameserver"
else
{
delv @"$Nameserver" "$UpstreamIP" "_dnsseckeys.""$Domain" TXT 2>/dev/null | \
sed -ne '/^;/d;s/^.*\sIN\s\+TXT\s\+"\(.*\)"$/'"$Domain"'.\tIN DNSKEY\t\1/p' | \
NormalizeZoneFileFormatting | sort
delv @"$Nameserver" "$UpstreamIP" "_cdskey.""$Domain" TXT 2>/dev/null | \
sed -ne '/^;/d;s/^.*\sIN\s\+TXT\s\+"\(.*\)"$/'"$Domain"'.\tIN CDS\t\1/p' | \
NormalizeZoneFileFormatting | sort
} > "$ZoneTempFolder""Keys.""$Nameserver"
fi
if [ -n "$(cat "$ZoneTempFolder""Keys.""$Nameserver")" ] && ! cmp -s "$ZoneTempFolder""Keys.""$Nameserver" "$ZoneTempFolder""OldKeys.""$Nameserver"; then
echo "1"
elif [ -f "$ZoneTempFolder""OldKeys.""$Nameserver" ]; then
mv -f "$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
{
delv @"${ChildServer##*\#}" "$UpstreamIP" "${ChildServer%%\#*}" CDS 2>/dev/null | \
sed -ne '/^;/d;s/^.*\sIN\s\+CDS\s\+\(.*\)$/'"${ChildServer%%\#*}"'.\tIN DS\t\1/p' | \
NormalizeZoneFileFormatting | sort
} > "$ZoneTempFolder""ChildKeys.""$ChildServer"
if [ -n "$(cat "$ZoneTempFolder""ChildKeys.""$ChildServer")" ]; then
sed -i -e '/\sIN\s\+DS\s\+0\s\+0\s\+0\s\+0/d' "$ZoneTempFolder""ChildKeys.""$ChildServer"
if ! cmp -s "$ZoneTempFolder""ChildKeys.""$ChildServer" "$ZoneTempFolder""OldChildKeys.""$ChildServer"; then
echo "1"
elif [ -z "$(cat "$ZoneTempFolder""ChildKeys.""$ChildServer")" ] && [ -f "$ZoneTempFolder""OldKeys.""$Nameserver" ]; then
echo "1"
fi
elif [ -f "$ZoneTempFolder""OldChildKeys.""$Nameserver" ]; then
mv -f "$ZoneTempFolder""OldChildKeys.""$ChildServer" "$ZoneTempFolder""ChildKeys.""$ChildServer"
fi
done
for KeyFile in "$ZoneTempFolder""Old"*; do
[ "$KeyFile" = "$ZoneTempFolder""Old*" ] || \
rm -f "$KeyFile"
done
}
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
ZoneFilesFolder="$3"
for Zone in $2; do
if [ -z "$ZoneFilesFolder" ] || [ -f "$ZoneFilesFolder""db.""$Zone"".""$1" ]; then
if [ -n "$DNSSCRIPT_DNSSECPolicy" ] && [ -n "$ZoneFilesFolder" ]; then
! rndc freeze "$1" IN "$Zone" >/dev/null
UnsignedZonefile="$ZoneFilesFolder""db.""$Zone"".""$1"
SignedZonefile="$ZoneFilesFolder""db.""$Zone"".""$1"".signed"
NewSerial="$(GetZoneFileSerial "$UnsignedZonefile")"
named-checkzone -q -i none -o "$TmpFolder""tmp.zone" "$1" "$UnsignedZonefile"
OldSerial="$(GetZoneFileSerial "$SignedZonefile")"
if [ $((NewSerial)) -le $((OldSerial)) ]; then
OldSerial=$((OldSerial+1))
sed -i -e 's/^\(\s*\S\+\s\+\([0-9]*\s\)\?\s*[Ii][Nn]\s\+[Ss][Oo][Aa]\s\+\S\+\s\+\S\+\s\+\)'"$NewSerial"'\(\s\+.*\)$/\1'"$OldSerial"'\3/g' "$TmpFolder""tmp.zone"
fi
cp -f "$TmpFolder""tmp.zone" "$SignedZonefile"
! rndc reload "$1" IN "$Zone" >/dev/null
! rndc thaw "$1" IN "$Zone" >/dev/null
elif ! 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"
fi
done
elif [ $((DNSSCRIPT_BIND_RELOAD_VER)) -eq 2 ]; then
/etc/init.d/named reload >/dev/null
fi
fi
}
UCharToFile () {
printf %b "\x$(printf %x "$1")" >> "$2"
}
GetDS () {
Owner="$(echo "$1" | sed -e 's/\./ /g')"
KeyTag="$2"
Protocol="$3"
Algo="$4"
KSK="$5"
TmpFolder="$6"
> "$TmpFolder""DSbin"
for SubString in $Owner; do
Length=${#SubString}
UCharToFile $((Length)) "$TmpFolder""DSbin"
echo -n "$SubString" >> "$TmpFolder""DSbin"
done
UCharToFile $((0)) "$TmpFolder""DSbin"
UCharToFile $((KeyTag / 256)) "$TmpFolder""DSbin"
UCharToFile $((KeyTag % 256)) "$TmpFolder""DSbin"
UCharToFile $((Protocol)) "$TmpFolder""DSbin"
UCharToFile $((Algo)) "$TmpFolder""DSbin"
echo "$KSK" | openssl base64 -d >> "$TmpFolder""DSbin"
sha256sum "$TmpFolder""DSbin" | sed -e 's/\s.*//g' | awk '{print toupper($0)}'
}
GetDSForZone () {
DNSSECKeyFolder="$1"
Domain="$2"
TmpFolder="$3"
if [ -n "$DNSSECKeyFolder" ];then
> "$TmpFolder""KSKRemoved"
> "$TmpFolder""KSK"
for OwnKeyFile in "$DNSSECKeyFolder""K""$Domain"".+"*".key"; do
if ! [ "$OwnKeyFile" = "$DNSSECKeyFolder""K""$Domain"".+*.key" ] && \
[ -n "$(sed -e '/^;/d;/^'"$Domain"'\.\s\+\([0-9]*\s\)\?\s*[Ii][Nn]\s\+[Dd][Nn][Ss][Kk][Ee][Yy]\s\+257/!d' "$OwnKeyFile")" ]; then
Removed="$(sed -ne 's/^; Delete: \(\S\{12\}\).*/\1/p' "$OwnKeyFile")"
RemovedSeconds="$(date -u -d "$Removed" '+%s' 2>/dev/null)"
if [ -z "$RemovedSeconds" ]; then
RemovedSeconds="$( echo "$Removed" | sed -ne 's/\(.\{4\}\)\(.\{2\}\)\(.\{2\}\)\(.\{2\}\)\(.\{2\}\).*/\1-\2-\3T\4:\5/p')"
RemovedSeconds="$(date -u -d "$RemovedSeconds" '+%s' 2>/dev/null)"
fi
Previous="$(cat "$TmpFolder""KSKRemoved")"
Previous="${Previous:-0}"
if [ $((RemovedSeconds)) -ge $((Previous)) ]; then
echo "$RemovedSeconds" > "$TmpFolder""KSKRemoved"
echo -n "$(echo "$OwnKeyFile" | sed -ne 's/^.\{'"$((${#DNSSECKeyFolder}+${#Domain}+2))"'\}+[^+]\++\(.*\)\.key/\1 /p')" > "$TmpFolder""KSK"
sed -ne '/^;/d;s/^'"$Domain"'\.\s\+\([0-9]*\s\)\?\s*[Ii][Nn]\s\+[Dd][Nn][Ss][Kk][Ee][Yy]\s\+\(.*\)$/\2/p' "$OwnKeyFile" >> "$TmpFolder""KSK"
fi
fi
done
KSK="$(cat "$TmpFolder""KSK")"
if [ -n "$KSK" ]; then
KeyID="$(echo "$KSK" | sed -e 's/ .*//g')"
KeyTag="$(echo "$KSK" | sed -e 's/^[^ ]* //g;s/ .*//g')"
Protocol="$(echo "$KSK" | sed -e 's/^[^ ]* [^ ]* //g;s/ .*//g')"
Algo="$(echo "$KSK" | sed -e 's/^[^ ]* [^ ]* [^ ]* //g;s/ .*//g')"
KSK="$(echo "$KSK" | sed -e 's/^[^ ]* [^ ]* [^ ]* [^ ]* //g;s/ //g')"
echo "_cdskey.""$Domain"". IN TXT \"""$KeyID"" ""$Algo"" 2 ""$(GetDS "$Domain" "$KeyTag" "$Protocol" "$Algo" "$KSK" "$TmpFolder")""\""
fi
fi
}
TraceErrAndExit() {
echo "$1" 1>&2
exit 1
}