1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108 |
- /*
- Technitium DNS Server
- Copyright (C) 2024 Shreyas Zare (shreyas@technitium.com)
- This program is free software: you can redistribute it and/or modify
- it under the terms of the GNU General Public License as published by
- the Free Software Foundation, either version 3 of the License, or
- (at your option) any later version.
- This program is distributed in the hope that it will be useful,
- but WITHOUT ANY WARRANTY; without even the implied warranty of
- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- GNU General Public License for more details.
- You should have received a copy of the GNU General Public License
- along with this program. If not, see <http://www.gnu.org/licenses/>.
- */
- using DnsServerCore.Dns.Zones;
- using System;
- using System.Collections.Generic;
- using System.Threading;
- using TechnitiumLibrary.Net.Dns.ResourceRecords;
- namespace DnsServerCore.Dns.Trees
- {
- class AuthZoneTree : ZoneTree<AuthZoneNode, SubDomainZone, ApexZone>
- {
- #region variables
- static readonly char[] _starPeriodTrimChars = new char[] { '*', '.' };
- #endregion
- #region private
- private static Node GetPreviousSubDomainZoneNode(byte[] key, Node currentNode, int baseDepth)
- {
- int k;
- NodeValue currentValue = currentNode.Value;
- if (currentValue is null)
- {
- //key value does not exists
- if (currentNode.Children is null)
- {
- //no children available; move to previous sibling
- k = currentNode.K - 1; //find previous node from sibling starting at k - 1
- currentNode = currentNode.Parent;
- }
- else
- {
- if (key.Length == currentNode.Depth)
- {
- //current node belongs to the key
- k = currentNode.K - 1; //find previous node from sibling starting at k - 1
- currentNode = currentNode.Parent;
- }
- else
- {
- //find the previous node for the given k in current node's children
- k = key[currentNode.Depth];
- }
- }
- }
- else
- {
- int x = DnsNSECRecordData.CanonicalComparison(currentValue.Key, key);
- if (x == 0)
- {
- //current node value matches the key
- k = currentNode.K - 1; //find previous node from sibling starting at k - 1
- currentNode = currentNode.Parent;
- }
- else if (x > 0)
- {
- //current node value is larger for the key
- k = currentNode.K - 1; //find previous node from sibling starting at k - 1
- currentNode = currentNode.Parent;
- }
- else
- {
- //current node value is smaller for the key
- if (currentNode.Children is null)
- {
- //the current node is previous node since no children exists and value is smaller for the key
- return currentNode;
- }
- else
- {
- //find the previous node for the given k in current node's children
- k = key[currentNode.Depth];
- }
- }
- }
- //start reverse tree traversal
- while ((currentNode is not null) && (currentNode.Depth >= baseDepth))
- {
- Node[] children = currentNode.Children;
- if (children is not null)
- {
- //find previous child node
- Node child = null;
- for (int i = k; i > -1; i--)
- {
- child = Volatile.Read(ref children[i]);
- if (child is not null)
- {
- bool childNodeHasApexZone = false;
- NodeValue childValue = child.Value;
- if (childValue is not null)
- {
- AuthZoneNode authZoneNode = childValue.Value;
- if (authZoneNode is not null)
- {
- if (authZoneNode.ApexZone is not null)
- childNodeHasApexZone = true; //must stop checking children of the apex of the sub zone
- }
- }
- if (!childNodeHasApexZone && child.Children is not null)
- break; //child has further children so check them first
- if (childValue is not null)
- {
- AuthZoneNode authZoneNode = childValue.Value;
- if (authZoneNode is not null)
- {
- if (authZoneNode.ParentSideZone is not null)
- {
- //is sub domain zone
- return child; //child has value so return it
- }
- if (authZoneNode.ApexZone is not null)
- {
- //is apex zone
- //skip to next child to avoid listing this auth zone's sub domains
- child = null; //set null to avoid child being set as current after the loop
- }
- }
- }
- }
- }
- if (child is not null)
- {
- //make found child as current
- k = children.Length - 1;
- currentNode = child;
- continue; //start over
- }
- }
- //no child node available; check for current node value
- {
- NodeValue value = currentNode.Value;
- if (value is not null)
- {
- AuthZoneNode authZoneNode = value.Value;
- if (authZoneNode is not null)
- {
- if ((authZoneNode.ApexZone is not null) && (currentNode.Depth == baseDepth))
- {
- //current node contains apex zone for the base depth i.e. current zone; return it
- return currentNode;
- }
- if (authZoneNode.ParentSideZone is not null)
- {
- //current node contains sub domain zone; return it
- return currentNode;
- }
- }
- }
- }
- //move up to parent node for previous sibling
- k = currentNode.K - 1;
- currentNode = currentNode.Parent;
- }
- return null;
- }
- private static Node GetNextSubDomainZoneNode(byte[] key, Node currentNode, int baseDepth)
- {
- int k;
- NodeValue currentValue = currentNode.Value;
- if (currentValue is null)
- {
- //key value does not exists
- if (currentNode.Children is null)
- {
- //no children available; move to next sibling
- k = currentNode.K + 1; //find next node from sibling starting at k + 1
- currentNode = currentNode.Parent;
- }
- else
- {
- if (key.Length == currentNode.Depth)
- {
- //current node belongs to the key
- k = 0; //find next node from first child of current node
- }
- else
- {
- //find next node for the given k in current node's children
- k = key[currentNode.Depth];
- }
- }
- }
- else
- {
- //check if node contains apex zone
- bool foundApexZone = false;
- if (currentNode.Depth > baseDepth)
- {
- AuthZoneNode authZoneNode = currentValue.Value;
- if (authZoneNode is not null)
- {
- ApexZone apexZone = authZoneNode.ApexZone;
- if (apexZone is not null)
- foundApexZone = true;
- }
- }
- if (foundApexZone)
- {
- //current contains apex for a sub zone; move up to parent node
- k = currentNode.K + 1; //find next node from sibling starting at k + 1
- currentNode = currentNode.Parent;
- }
- else
- {
- int x = DnsNSECRecordData.CanonicalComparison(currentValue.Key, key);
- if (x == 0)
- {
- //current node value matches the key
- k = 0; //find next node from children starting at k
- }
- else if (x > 0)
- {
- //current node value is larger for the key thus current is the next node
- return currentNode;
- }
- else
- {
- //current node value is smaller for the key
- k = key[currentNode.Depth]; //find next node from children starting at k = key[depth]
- }
- }
- }
- //start tree traversal
- while ((currentNode is not null) && (currentNode.Depth >= baseDepth))
- {
- Node[] children = currentNode.Children;
- if (children is not null)
- {
- //find next child node
- Node child = null;
- for (int i = k; i < children.Length; i++)
- {
- child = Volatile.Read(ref children[i]);
- if (child is not null)
- {
- NodeValue childValue = child.Value;
- if (childValue is not null)
- {
- AuthZoneNode authZoneNode = childValue.Value;
- if (authZoneNode is not null)
- {
- if (authZoneNode.ParentSideZone is not null)
- {
- //is sub domain zone
- return child; //child has value so return it
- }
- if (authZoneNode.ApexZone is not null)
- {
- //is apex zone
- //skip to next child to avoid listing this auth zone's sub domains
- child = null; //set null to avoid child being set as current after the loop
- continue;
- }
- }
- }
- if (child.Children is not null)
- break;
- }
- }
- if (child is not null)
- {
- //make found child as current
- k = 0;
- currentNode = child;
- continue; //start over
- }
- }
- //no child nodes available; move up to parent node
- k = currentNode.K + 1;
- currentNode = currentNode.Parent;
- }
- return null;
- }
- private static bool SubDomainExists(byte[] key, Node currentNode)
- {
- Node[] children = currentNode.Children;
- if (children is not null)
- {
- Node child = Volatile.Read(ref children[1]); //[*]
- if (child is not null)
- return true; //wildcard exists so subdomain name exists: RFC 4592 section 4.9
- }
- Node nextSubDomain = GetNextSubDomainZoneNode(key, currentNode, currentNode.Depth);
- if (nextSubDomain is null)
- return false;
- NodeValue value = nextSubDomain.Value;
- if (value is null)
- return false;
- return IsKeySubDomain(key, value.Key, false);
- }
- private static AuthZone GetAuthZoneFromNode(Node node, string zoneName)
- {
- NodeValue value = node.Value;
- if (value is not null)
- {
- AuthZoneNode authZoneNode = value.Value;
- if (authZoneNode is not null)
- return authZoneNode.GetAuthZone(zoneName);
- }
- return null;
- }
- private void RemoveAllSubDomains(string domain, Node currentNode)
- {
- //remove all sub domains under current zone
- Node current = currentNode;
- byte[] currentKey = ConvertToByteKey(domain);
- do
- {
- current = GetNextSubDomainZoneNode(currentKey, current, currentNode.Depth);
- if (current is null)
- break;
- NodeValue v = current.Value;
- if (v is not null)
- {
- AuthZoneNode z = v.Value;
- if (z is not null)
- {
- if (z.ApexZone is null)
- {
- //no apex zone at this node; remove complete zone node
- current.RemoveNodeValue(v.Key, out _); //remove node value
- current.CleanThisBranch();
- }
- else
- {
- //apex node exists; remove parent size sub domain
- z.TryRemove(out SubDomainZone _);
- }
- }
- currentKey = v.Key;
- }
- }
- while (true);
- }
- #endregion
- #region protected
- protected override void GetClosestValuesForZone(AuthZoneNode zoneValue, out SubDomainZone closestSubDomain, out SubDomainZone closestDelegation, out ApexZone closestAuthority)
- {
- ApexZone apexZone = zoneValue.ApexZone;
- if (apexZone is not null)
- {
- //hosted primary/secondary/stub/forwarder zone found
- closestSubDomain = null;
- closestDelegation = zoneValue.ParentSideZone;
- closestAuthority = apexZone;
- }
- else
- {
- //hosted sub domain
- SubDomainZone subDomainZone = zoneValue.ParentSideZone;
- if (subDomainZone.ContainsNameServerRecords())
- {
- //delegated sub domain found
- closestSubDomain = null;
- closestDelegation = subDomainZone;
- }
- else
- {
- closestSubDomain = subDomainZone;
- closestDelegation = null;
- }
- closestAuthority = null;
- }
- }
- #endregion
- #region public
- public bool TryAdd(ApexZone zone)
- {
- AuthZoneNode zoneNode = GetOrAdd(zone.Name, delegate (string key)
- {
- return new AuthZoneNode(null, zone);
- });
- if (ReferenceEquals(zoneNode.ApexZone, zone))
- return true; //added successfully
- return zoneNode.TryAdd(zone);
- }
- public bool TryGet(string zoneName, string domain, out AuthZone authZone)
- {
- if (TryGet(domain, out AuthZoneNode authZoneNode))
- {
- authZone = authZoneNode.GetAuthZone(zoneName);
- return authZone is not null;
- }
- authZone = null;
- return false;
- }
- public bool TryGet(string zoneName, out ApexZone apexZone)
- {
- if (TryGet(zoneName, out AuthZoneNode authZoneNode) && (authZoneNode.ApexZone is not null))
- {
- apexZone = authZoneNode.ApexZone;
- return true;
- }
- apexZone = null;
- return false;
- }
- public bool TryRemove(string domain, out ApexZone apexZone)
- {
- if (!TryGet(domain, out AuthZoneNode authZoneNode, out Node currentNode) || (authZoneNode.ApexZone is null))
- {
- apexZone = null;
- return false;
- }
- apexZone = authZoneNode.ApexZone;
- if (authZoneNode.ParentSideZone is null)
- {
- //remove complete zone node
- if (!base.TryRemove(domain, out AuthZoneNode _))
- {
- apexZone = null;
- return false;
- }
- }
- else
- {
- //parent side sub domain exists; remove only apex zone from zone node
- if (!authZoneNode.TryRemove(out ApexZone _))
- {
- apexZone = null;
- return false;
- }
- }
- //remove all sub domains under current apex zone
- RemoveAllSubDomains(domain, currentNode);
- currentNode.CleanThisBranch();
- return true;
- }
- public bool TryRemove(string domain, out SubDomainZone subDomainZone, bool removeAllSubDomains = false)
- {
- if (!TryGet(domain, out AuthZoneNode zoneNode, out Node currentNode) || (zoneNode.ParentSideZone is null))
- {
- subDomainZone = null;
- return false;
- }
- subDomainZone = zoneNode.ParentSideZone;
- if (zoneNode.ApexZone is null)
- {
- //remove complete zone node
- if (!base.TryRemove(domain, out AuthZoneNode _))
- {
- subDomainZone = null;
- return false;
- }
- }
- else
- {
- //apex zone exists; remove only parent side sub domain from zone node
- if (!zoneNode.TryRemove(out SubDomainZone _))
- {
- subDomainZone = null;
- return false;
- }
- }
- if (removeAllSubDomains)
- RemoveAllSubDomains(domain, currentNode); //remove all sub domains under current subdomain zone
- currentNode.CleanThisBranch();
- return true;
- }
- public override bool TryRemove(string key, out AuthZoneNode authZoneNode)
- {
- throw new InvalidOperationException();
- }
- public IReadOnlyList<AuthZone> GetApexZoneWithSubDomainZones(string zoneName)
- {
- List<AuthZone> zones = new List<AuthZone>();
- byte[] key = ConvertToByteKey(zoneName);
- NodeValue nodeValue = _root.FindNodeValue(key, out Node currentNode);
- if (nodeValue is not null)
- {
- AuthZoneNode authZoneNode = nodeValue.Value;
- if (authZoneNode is not null)
- {
- ApexZone apexZone = authZoneNode.ApexZone;
- if (apexZone is not null)
- {
- zones.Add(apexZone);
- Node current = currentNode;
- byte[] currentKey = key;
- do
- {
- current = GetNextSubDomainZoneNode(currentKey, current, currentNode.Depth);
- if (current is null)
- break;
- NodeValue value = current.Value;
- if (value is not null)
- {
- authZoneNode = value.Value;
- if (authZoneNode is not null)
- zones.Add(authZoneNode.ParentSideZone);
- currentKey = value.Key;
- }
- }
- while (true);
- }
- }
- }
- return zones;
- }
- public IReadOnlyList<AuthZone> GetSubDomainZoneWithSubDomainZones(string domain)
- {
- List<AuthZone> zones = new List<AuthZone>();
- byte[] key = ConvertToByteKey(domain);
- NodeValue nodeValue = _root.FindNodeValue(key, out Node currentNode);
- if (nodeValue is not null)
- {
- AuthZoneNode authZoneNode = nodeValue.Value;
- if (authZoneNode is not null)
- {
- SubDomainZone subDomainZone = authZoneNode.ParentSideZone;
- if (subDomainZone is not null)
- {
- zones.Add(subDomainZone);
- Node current = currentNode;
- byte[] currentKey = key;
- do
- {
- current = GetNextSubDomainZoneNode(currentKey, current, currentNode.Depth);
- if (current is null)
- break;
- NodeValue value = current.Value;
- if (value is not null)
- {
- authZoneNode = value.Value;
- if (authZoneNode is not null)
- zones.Add(authZoneNode.ParentSideZone);
- currentKey = value.Key;
- }
- }
- while (true);
- }
- }
- }
- return zones;
- }
- public AuthZone GetOrAddSubDomainZone(string zoneName, string domain, Func<SubDomainZone> valueFactory)
- {
- bool isApex = zoneName.Equals(domain, StringComparison.OrdinalIgnoreCase);
- AuthZoneNode authZoneNode = GetOrAdd(domain, delegate (string key)
- {
- if (isApex)
- throw new DnsServerException("Zone was not found for domain: " + key);
- return new AuthZoneNode(valueFactory(), null);
- });
- if (isApex)
- {
- if (authZoneNode.ApexZone is null)
- throw new DnsServerException("Zone was not found: " + zoneName);
- return authZoneNode.ApexZone;
- }
- else
- {
- return authZoneNode.GetOrAddParentSideZone(valueFactory);
- }
- }
- public AuthZone GetAuthZone(string zoneName, string domain)
- {
- if (TryGet(domain, out AuthZoneNode authZoneNode))
- return authZoneNode.GetAuthZone(zoneName);
- return null;
- }
- public ApexZone GetApexZone(string zoneName)
- {
- if (TryGet(zoneName, out AuthZoneNode authZoneNode))
- return authZoneNode.ApexZone;
- return null;
- }
- public AuthZone FindZone(string domain, out SubDomainZone closest, out SubDomainZone delegation, out ApexZone authority, out bool hasSubDomains)
- {
- byte[] key = ConvertToByteKey(domain);
- AuthZoneNode authZoneNode = FindZoneNode(key, true, out Node currentNode, out Node closestSubDomainNode, out _, out SubDomainZone closestSubDomain, out SubDomainZone closestDelegation, out ApexZone closestAuthority);
- if (authZoneNode is null)
- {
- //zone not found
- closest = closestSubDomain;
- delegation = closestDelegation;
- authority = closestAuthority;
- if (authority is null)
- {
- //no authority so no sub domains
- hasSubDomains = false;
- }
- else if ((closestSubDomainNode is not null) && !closestSubDomainNode.HasChildren)
- {
- //closest sub domain node does not have any children so no sub domains
- hasSubDomains = false;
- }
- else
- {
- //check if current node has sub domains
- hasSubDomains = SubDomainExists(key, currentNode);
- }
- return null;
- }
- else
- {
- //zone found
- AuthZone zone;
- ApexZone apexZone = authZoneNode.ApexZone;
- if (apexZone is not null)
- {
- zone = apexZone;
- closest = null;
- delegation = authZoneNode.ParentSideZone;
- authority = apexZone;
- }
- else
- {
- SubDomainZone subDomainZone = authZoneNode.ParentSideZone;
- zone = subDomainZone;
- if (zone == closestSubDomain)
- closest = null;
- else
- closest = closestSubDomain;
- if (closestDelegation is not null)
- delegation = closestDelegation;
- else if (subDomainZone.ContainsNameServerRecords())
- delegation = subDomainZone;
- else
- delegation = null;
- authority = closestAuthority;
- }
- if (zone.Disabled)
- {
- if ((closestSubDomainNode is not null) && !closestSubDomainNode.HasChildren)
- {
- //closest sub domain node does not have any children so no sub domains
- hasSubDomains = false;
- }
- else
- {
- //check if current node has sub domains
- hasSubDomains = SubDomainExists(key, currentNode);
- }
- }
- else
- {
- //since zone is found, it does not matter if subdomain exists or not
- hasSubDomains = false;
- }
- return zone;
- }
- }
- public AuthZone FindPreviousSubDomainZone(string zoneName, string domain)
- {
- byte[] key = ConvertToByteKey(domain);
- AuthZoneNode authZoneNode = FindZoneNode(key, false, out Node currentNode, out _, out Node closestAuthorityNode, out _, out _, out _);
- if (authZoneNode is not null)
- {
- //zone exists
- ApexZone apexZone = authZoneNode.ApexZone;
- SubDomainZone parentSideZone = authZoneNode.ParentSideZone;
- if ((apexZone is not null) && (parentSideZone is not null))
- {
- //found ambiguity between apex zone and sub domain zone
- if (!apexZone.Name.Equals(zoneName, StringComparison.OrdinalIgnoreCase))
- {
- //zone name does not match with apex zone and thus not match with closest authority node
- //find the closest authority zone for given zone name
- if (!TryGet(zoneName, out _, out Node closestNodeForZoneName))
- throw new InvalidOperationException();
- closestAuthorityNode = closestNodeForZoneName;
- }
- }
- }
- Node previousNode = GetPreviousSubDomainZoneNode(key, currentNode, closestAuthorityNode.Depth);
- if (previousNode is not null)
- {
- AuthZone authZone = GetAuthZoneFromNode(previousNode, zoneName);
- if (authZone is not null)
- return authZone;
- }
- return null;
- }
- public AuthZone FindNextSubDomainZone(string zoneName, string domain)
- {
- byte[] key = ConvertToByteKey(domain);
- AuthZoneNode authZoneNode = FindZoneNode(key, false, out Node currentNode, out _, out Node closestAuthorityNode, out _, out _, out _);
- if (authZoneNode is not null)
- {
- //zone exists
- ApexZone apexZone = authZoneNode.ApexZone;
- SubDomainZone parentSideZone = authZoneNode.ParentSideZone;
- if ((apexZone is not null) && (parentSideZone is not null))
- {
- //found ambiguity between apex zone and sub domain zone
- if (!apexZone.Name.Equals(zoneName, StringComparison.OrdinalIgnoreCase))
- {
- //zone name does not match with apex zone and thus not match with closest authority node
- //find the closest authority zone for given zone name
- if (!TryGet(zoneName, out _, out Node closestNodeForZoneName))
- throw new InvalidOperationException();
- closestAuthorityNode = closestNodeForZoneName;
- }
- }
- }
- Node nextNode = GetNextSubDomainZoneNode(key, currentNode, closestAuthorityNode.Depth);
- if (nextNode is not null)
- {
- AuthZone authZone = GetAuthZoneFromNode(nextNode, zoneName);
- if (authZone is not null)
- return authZone;
- }
- return null;
- }
- public bool SubDomainExistsFor(string zoneName, string domain)
- {
- AuthZone nextAuthZone = FindNextSubDomainZone(zoneName, domain);
- if (nextAuthZone is null)
- return false;
- return nextAuthZone.Name.EndsWith("." + domain);
- }
- #endregion
- #region DNSSEC
- public IReadOnlyList<DnsResourceRecord> FindNSecProofOfNonExistenceNxDomain(string domain, bool isWildcardAnswer)
- {
- List<DnsResourceRecord> nsecRecords = new List<DnsResourceRecord>(2 * 2);
- void AddProofOfCoverFor(string domain)
- {
- byte[] key = ConvertToByteKey(domain);
- AuthZoneNode authZoneNode = FindZoneNode(key, false, out Node currentNode, out _, out Node closestAuthorityNode, out _, out _, out ApexZone closestAuthority);
- if (authZoneNode is not null)
- throw new InvalidOperationException(); //domain exists! cannot prove non-existence
- Node previousNode = GetPreviousSubDomainZoneNode(key, currentNode, closestAuthorityNode.Depth);
- if (previousNode is not null)
- {
- AuthZone authZone = GetAuthZoneFromNode(previousNode, closestAuthority.Name);
- if (authZone is not null)
- {
- IReadOnlyList<DnsResourceRecord> proofOfCoverRecords = authZone.QueryRecords(DnsResourceRecordType.NSEC, true);
- foreach (DnsResourceRecord proofOfCoverRecord in proofOfCoverRecords)
- {
- if (!nsecRecords.Contains(proofOfCoverRecord))
- nsecRecords.Add(proofOfCoverRecord);
- }
- }
- }
- }
- //add proof of cover for domain
- AddProofOfCoverFor(domain);
- if (isWildcardAnswer)
- return nsecRecords;
- //add proof of cover for wildcard
- if (nsecRecords.Count > 0)
- {
- //add wildcard proof to prove that a wildcard expansion was not possible
- DnsResourceRecord nsecRecord = nsecRecords[0];
- DnsNSECRecordData nsec = nsecRecord.RDATA as DnsNSECRecordData;
- string wildcardName = DnsNSECRecordData.GetWildcardFor(nsecRecord, domain);
- if (!DnsNSECRecordData.IsDomainCovered(nsecRecord.Name, nsec.NextDomainName, wildcardName))
- AddProofOfCoverFor(wildcardName);
- }
- return nsecRecords;
- }
- public IReadOnlyList<DnsResourceRecord> FindNSec3ProofOfNonExistenceNxDomain(string domain, bool isWildcardAnswer, CancellationToken cancellationToken)
- {
- List<DnsResourceRecord> nsec3Records = new List<DnsResourceRecord>(3 * 2);
- void AddProofOfCoverFor(string hashedOwnerName, string zoneName)
- {
- //find previous NSEC3 for the hashed owner name
- IReadOnlyList<DnsResourceRecord> proofOfCoverRecords = null;
- string currentOwnerName = hashedOwnerName;
- while (true)
- {
- cancellationToken.ThrowIfCancellationRequested();
- AuthZone previousNSec3Zone = FindPreviousSubDomainZone(zoneName, currentOwnerName);
- if (previousNSec3Zone is null)
- break;
- IReadOnlyList<DnsResourceRecord> previousNSec3Records = previousNSec3Zone.QueryRecords(DnsResourceRecordType.NSEC3, true);
- if (previousNSec3Records.Count > 0)
- {
- proofOfCoverRecords = previousNSec3Records;
- break;
- }
- currentOwnerName = previousNSec3Zone.Name;
- }
- if (proofOfCoverRecords is null)
- {
- //didnt find previous NSEC3; find the last NSEC3
- currentOwnerName = hashedOwnerName;
- while (true)
- {
- cancellationToken.ThrowIfCancellationRequested();
- AuthZone nextNSec3Zone = GetAuthZone(zoneName, currentOwnerName);
- if (nextNSec3Zone is null)
- {
- nextNSec3Zone = FindNextSubDomainZone(zoneName, currentOwnerName);
- if (nextNSec3Zone is null)
- break;
- }
- IReadOnlyList<DnsResourceRecord> nextNSec3Records = nextNSec3Zone.QueryRecords(DnsResourceRecordType.NSEC3, true);
- if (nextNSec3Records.Count > 0)
- {
- proofOfCoverRecords = nextNSec3Records;
- DnsResourceRecord previousNSec3Record = nextNSec3Records[0];
- string nextHashedOwnerNameString = (previousNSec3Record.RDATA as DnsNSEC3RecordData).NextHashedOwnerName + (zoneName.Length > 0 ? "." + zoneName : "");
- if (DnsNSECRecordData.CanonicalComparison(previousNSec3Record.Name, nextHashedOwnerNameString) >= 0)
- break; //found last NSEC3
- //jump to next hashed owner
- currentOwnerName = nextHashedOwnerNameString;
- }
- else
- {
- currentOwnerName = nextNSec3Zone.Name;
- }
- }
- }
- if (proofOfCoverRecords is null)
- throw new InvalidOperationException();
- foreach (DnsResourceRecord proofOfCoverRecord in proofOfCoverRecords)
- {
- if (!nsec3Records.Contains(proofOfCoverRecord))
- nsec3Records.Add(proofOfCoverRecord);
- }
- }
- byte[] key = ConvertToByteKey(domain);
- string closestEncloser;
- AuthZoneNode authZoneNode = FindZoneNode(key, isWildcardAnswer, out _, out _, out _, out SubDomainZone closestSubDomain, out _, out ApexZone closestAuthority);
- if (authZoneNode is not null)
- {
- if (isWildcardAnswer && (closestSubDomain is not null) && closestSubDomain.Name.StartsWith('*'))
- {
- closestEncloser = closestSubDomain.Name.TrimStart(_starPeriodTrimChars);
- }
- else
- {
- //subdomain that contains only NSEC3 record does not really exists: RFC5155 section 7.2.8
- if ((authZoneNode.ApexZone is not null) || ((authZoneNode.ParentSideZone is not null) && !authZoneNode.ParentSideZone.HasOnlyNSec3Records()))
- throw new InvalidOperationException(); //domain exists! cannot prove non-existence
- //continue to prove non-existence of this nsec3 owner name
- closestEncloser = closestAuthority.Name;
- }
- }
- else
- {
- if (closestSubDomain is not null)
- closestEncloser = closestSubDomain.Name;
- else if (closestAuthority is not null)
- closestEncloser = closestAuthority.Name;
- else
- throw new InvalidOperationException(); //cannot find closest encloser
- }
- IReadOnlyList<DnsResourceRecord> nsec3ParamRecords = closestAuthority.GetRecords(DnsResourceRecordType.NSEC3PARAM);
- if (nsec3ParamRecords.Count == 0)
- throw new InvalidOperationException("Zone does not have NSEC3 deployed.");
- DnsNSEC3PARAMRecordData nsec3Param = nsec3ParamRecords[0].RDATA as DnsNSEC3PARAMRecordData;
- //find correct closest encloser
- string hashedNextCloserName;
- while (true)
- {
- cancellationToken.ThrowIfCancellationRequested();
- string nextCloserName = DnsNSEC3RecordData.GetNextCloserName(domain, closestEncloser);
- hashedNextCloserName = nsec3Param.ComputeHashedOwnerNameBase32HexString(nextCloserName) + (closestAuthority.Name.Length > 0 ? "." + closestAuthority.Name : "");
- AuthZone nsec3Zone = GetAuthZone(closestAuthority.Name, hashedNextCloserName);
- if (nsec3Zone is null)
- break; //next closer name does not exists
- //next closer name exists as an ENT
- closestEncloser = nextCloserName;
- if (domain.Equals(closestEncloser, StringComparison.OrdinalIgnoreCase))
- {
- //domain exists as an ENT; return no data proof
- return FindNSec3ProofOfNonExistenceNoData(nsec3Zone);
- }
- }
- if (isWildcardAnswer)
- {
- //add proof of cover for the domain to prove non-existence (wildcard)
- AddProofOfCoverFor(hashedNextCloserName, closestAuthority.Name);
- }
- else
- {
- //add closest encloser proof
- string hashedClosestEncloser = nsec3Param.ComputeHashedOwnerNameBase32HexString(closestEncloser) + (closestAuthority.Name.Length > 0 ? "." + closestAuthority.Name : "");
- AuthZone nsec3Zone = GetAuthZone(closestAuthority.Name, hashedClosestEncloser);
- if (nsec3Zone is null)
- throw new InvalidOperationException();
- IReadOnlyList<DnsResourceRecord> closestEncloserProofRecords = nsec3Zone.QueryRecords(DnsResourceRecordType.NSEC3, true);
- if (closestEncloserProofRecords.Count == 0)
- throw new InvalidOperationException();
- nsec3Records.AddRange(closestEncloserProofRecords);
- DnsResourceRecord closestEncloserProofRecord = closestEncloserProofRecords[0];
- DnsNSEC3RecordData closestEncloserProof = closestEncloserProofRecord.RDATA as DnsNSEC3RecordData;
- //add proof of cover for the next closer name
- if (!DnsNSECRecordData.IsDomainCovered(closestEncloserProofRecord.Name, closestEncloserProof.NextHashedOwnerName + (closestAuthority.Name.Length > 0 ? "." + closestAuthority.Name : ""), hashedNextCloserName))
- AddProofOfCoverFor(hashedNextCloserName, closestAuthority.Name);
- //add proof of cover to prove that a wildcard expansion was not possible
- string wildcardDomain = closestEncloser.Length > 0 ? "*." + closestEncloser : "*";
- string hashedWildcardDomainName = nsec3Param.ComputeHashedOwnerNameBase32HexString(wildcardDomain) + (closestAuthority.Name.Length > 0 ? "." + closestAuthority.Name : "");
- if (!DnsNSECRecordData.IsDomainCovered(closestEncloserProofRecord.Name, closestEncloserProof.NextHashedOwnerName + (closestAuthority.Name.Length > 0 ? "." + closestAuthority.Name : ""), hashedWildcardDomainName))
- AddProofOfCoverFor(hashedWildcardDomainName, closestAuthority.Name);
- }
- return nsec3Records;
- }
- public static IReadOnlyList<DnsResourceRecord> FindNSecProofOfNonExistenceNoData(AuthZone zone)
- {
- IReadOnlyList<DnsResourceRecord> nsecRecords = zone.QueryRecords(DnsResourceRecordType.NSEC, true);
- if (nsecRecords.Count == 0)
- throw new InvalidOperationException("Zone does not have NSEC deployed correctly.");
- return nsecRecords;
- }
- public IReadOnlyList<DnsResourceRecord> FindNSec3ProofOfNonExistenceNoData(AuthZone zone, ApexZone apexZone, CancellationToken cancellationToken)
- {
- IReadOnlyList<DnsResourceRecord> nsec3ParamRecords = apexZone.GetRecords(DnsResourceRecordType.NSEC3PARAM);
- if (nsec3ParamRecords.Count == 0)
- throw new InvalidOperationException("Zone does not have NSEC3 deployed.");
- DnsNSEC3PARAMRecordData nsec3Param = nsec3ParamRecords[0].RDATA as DnsNSEC3PARAMRecordData;
- string hashedOwnerName = nsec3Param.ComputeHashedOwnerNameBase32HexString(zone.Name) + (apexZone.Name.Length > 0 ? "." + apexZone.Name : "");
- AuthZone nsec3Zone = GetAuthZone(apexZone.Name, hashedOwnerName);
- if (nsec3Zone is null)
- {
- //this is probably since the domain in request is for an nsec3 record owner name
- return FindNSec3ProofOfNonExistenceNxDomain(zone.Name, false, cancellationToken);
- }
- return FindNSec3ProofOfNonExistenceNoData(nsec3Zone);
- }
- private static IReadOnlyList<DnsResourceRecord> FindNSec3ProofOfNonExistenceNoData(AuthZone nsec3Zone)
- {
- IReadOnlyList<DnsResourceRecord> nsec3Records = nsec3Zone.QueryRecords(DnsResourceRecordType.NSEC3, true);
- if (nsec3Records.Count > 0)
- return nsec3Records;
- return Array.Empty<DnsResourceRecord>();
- }
- #endregion
- }
- }
|