mirror of
https://github.com/fergalmoran/DnsServer.git
synced 2026-02-11 18:33:58 +00:00
1513 lines
55 KiB
C#
1513 lines
55 KiB
C#
/*
|
|
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.ResourceRecords;
|
|
using System;
|
|
using System.Collections.Generic;
|
|
using System.Net;
|
|
using System.Threading;
|
|
using System.Threading.Tasks;
|
|
using TechnitiumLibrary.Net;
|
|
using TechnitiumLibrary.Net.Dns;
|
|
using TechnitiumLibrary.Net.Dns.ResourceRecords;
|
|
|
|
namespace DnsServerCore.Dns.Zones
|
|
{
|
|
public enum AuthZoneQueryAccess : byte
|
|
{
|
|
Deny = 0,
|
|
Allow = 1,
|
|
AllowOnlyPrivateNetworks = 2,
|
|
AllowOnlyZoneNameServers = 3,
|
|
UseSpecifiedNetworkACL = 4,
|
|
AllowZoneNameServersAndUseSpecifiedNetworkACL = 5
|
|
}
|
|
|
|
public enum AuthZoneTransfer : byte
|
|
{
|
|
Deny = 0,
|
|
Allow = 1,
|
|
AllowOnlyZoneNameServers = 2,
|
|
UseSpecifiedNetworkACL = 3,
|
|
AllowZoneNameServersAndUseSpecifiedNetworkACL = 4
|
|
}
|
|
|
|
public enum AuthZoneNotify : byte
|
|
{
|
|
None = 0,
|
|
ZoneNameServers = 1,
|
|
SpecifiedNameServers = 2,
|
|
BothZoneAndSpecifiedNameServers = 3,
|
|
SeparateNameServersForCatalogAndMemberZones = 4
|
|
}
|
|
|
|
public enum AuthZoneUpdate : byte
|
|
{
|
|
Deny = 0,
|
|
Allow = 1,
|
|
AllowOnlyZoneNameServers = 2,
|
|
UseSpecifiedNetworkACL = 3,
|
|
AllowZoneNameServersAndUseSpecifiedNetworkACL = 4
|
|
}
|
|
|
|
abstract class ApexZone : AuthZone, IDisposable
|
|
{
|
|
#region variables
|
|
|
|
protected readonly DnsServer _dnsServer;
|
|
protected DateTime _lastModified;
|
|
|
|
string _catalogZoneName;
|
|
bool _overrideCatalogQueryAccess;
|
|
bool _overrideCatalogZoneTransfer;
|
|
bool _overrideCatalogNotify;
|
|
|
|
protected AuthZoneQueryAccess _queryAccess;
|
|
IReadOnlyCollection<NetworkAccessControl> _queryAccessNetworkACL;
|
|
|
|
protected AuthZoneTransfer _zoneTransfer;
|
|
IReadOnlyCollection<NetworkAccessControl> _zoneTransferNetworkACL;
|
|
IReadOnlyDictionary<string, object> _zoneTransferTsigKeyNames;
|
|
readonly List<DnsResourceRecord> _zoneHistory; //for IXFR support
|
|
|
|
AuthZoneNotify _notify;
|
|
IReadOnlyCollection<IPAddress> _notifyNameServers;
|
|
IReadOnlyCollection<IPAddress> _notifySecondaryCatalogNameServers;
|
|
|
|
AuthZoneUpdate _update;
|
|
IReadOnlyCollection<NetworkAccessControl> _updateNetworkACL;
|
|
IReadOnlyDictionary<string, IReadOnlyDictionary<string, IReadOnlyList<DnsResourceRecordType>>> _updateSecurityPolicies;
|
|
|
|
protected AuthZoneDnssecStatus _dnssecStatus;
|
|
|
|
Timer _notifyTimer;
|
|
bool _notifyTimerTriggered;
|
|
const int NOTIFY_TIMER_INTERVAL = 10000;
|
|
List<string> _notifyList;
|
|
List<string> _notifyFailed;
|
|
const int NOTIFY_TIMEOUT = 10000;
|
|
const int NOTIFY_RETRIES = 5;
|
|
|
|
protected bool _syncFailed;
|
|
|
|
Timer _recordExpiryTimer;
|
|
readonly object _recordExpiryTimerLock = new object();
|
|
DateTime _recordExpiryTimerStartedOn;
|
|
uint _recordExpiryTimerTtl;
|
|
bool _recordExpiryTimerRunning;
|
|
|
|
CatalogZone _catalogZone;
|
|
SecondaryCatalogZone _secondaryCatalogZone;
|
|
|
|
#endregion
|
|
|
|
#region constructor
|
|
|
|
protected ApexZone(DnsServer dnsServer, AuthZoneInfo zoneInfo)
|
|
: base(zoneInfo)
|
|
{
|
|
_dnsServer = dnsServer;
|
|
|
|
_catalogZoneName = zoneInfo.CatalogZoneName;
|
|
_overrideCatalogQueryAccess = zoneInfo.OverrideCatalogQueryAccess;
|
|
_overrideCatalogZoneTransfer = zoneInfo.OverrideCatalogZoneTransfer;
|
|
_overrideCatalogNotify = zoneInfo.OverrideCatalogNotify;
|
|
|
|
_queryAccess = zoneInfo.QueryAccess;
|
|
_queryAccessNetworkACL = zoneInfo.QueryAccessNetworkACL;
|
|
|
|
_zoneTransfer = zoneInfo.ZoneTransfer;
|
|
_zoneTransferNetworkACL = zoneInfo.ZoneTransferNetworkACL;
|
|
_zoneTransferTsigKeyNames = zoneInfo.ZoneTransferTsigKeyNames;
|
|
|
|
if (zoneInfo.ZoneHistory is null)
|
|
_zoneHistory = new List<DnsResourceRecord>();
|
|
else
|
|
_zoneHistory = new List<DnsResourceRecord>(zoneInfo.ZoneHistory);
|
|
|
|
_notify = zoneInfo.Notify;
|
|
_notifyNameServers = zoneInfo.NotifyNameServers;
|
|
_notifySecondaryCatalogNameServers = zoneInfo.NotifySecondaryCatalogNameServers;
|
|
|
|
_update = zoneInfo.Update;
|
|
_updateNetworkACL = zoneInfo.UpdateNetworkACL;
|
|
_updateSecurityPolicies = zoneInfo.UpdateSecurityPolicies;
|
|
|
|
_lastModified = zoneInfo.LastModified;
|
|
}
|
|
|
|
protected ApexZone(DnsServer dnsServer, string name)
|
|
: base(name)
|
|
{
|
|
_dnsServer = dnsServer;
|
|
|
|
_queryAccess = AuthZoneQueryAccess.Allow;
|
|
_zoneHistory = new List<DnsResourceRecord>();
|
|
|
|
_lastModified = DateTime.UtcNow;
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region IDisposable
|
|
|
|
bool _disposed;
|
|
|
|
protected virtual void Dispose(bool disposing)
|
|
{
|
|
if (_disposed)
|
|
return;
|
|
|
|
if (disposing)
|
|
{
|
|
_notifyTimer?.Dispose();
|
|
|
|
lock (_recordExpiryTimerLock)
|
|
{
|
|
if (_recordExpiryTimer is not null)
|
|
{
|
|
_recordExpiryTimer.Dispose();
|
|
_recordExpiryTimer = null;
|
|
}
|
|
}
|
|
}
|
|
|
|
_disposed = true;
|
|
}
|
|
|
|
public void Dispose()
|
|
{
|
|
Dispose(true);
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region notify
|
|
|
|
protected void InitNotify()
|
|
{
|
|
_notifyTimer = new Timer(NotifyTimerCallback, null, Timeout.Infinite, Timeout.Infinite);
|
|
_notifyList = new List<string>();
|
|
_notifyFailed = new List<string>();
|
|
}
|
|
|
|
protected void DisableNotifyTimer()
|
|
{
|
|
if (_notifyTimer is not null)
|
|
_notifyTimer.Change(Timeout.Infinite, Timeout.Infinite);
|
|
}
|
|
|
|
private async void NotifyTimerCallback(object state)
|
|
{
|
|
ApexZone apexZone = this;
|
|
|
|
if ((apexZone.CatalogZone is not null) && !apexZone.OverrideCatalogNotify)
|
|
apexZone = apexZone.CatalogZone;
|
|
|
|
List<string> notifiedNameServers = new List<string>();
|
|
|
|
async Task NotifyZoneNameServersAsync(bool onlyFailedNameServers)
|
|
{
|
|
string primaryNameServer = (_entries[DnsResourceRecordType.SOA][0].RDATA as DnsSOARecordData).PrimaryNameServer;
|
|
IReadOnlyList<DnsResourceRecord> nsRecords = GetRecords(DnsResourceRecordType.NS); //stub zone has no authority so cant use QueryRecords
|
|
|
|
//notify all secondary name servers
|
|
List<Task> tasks = new List<Task>();
|
|
|
|
foreach (DnsResourceRecord nsRecord in nsRecords)
|
|
{
|
|
if (nsRecord.GetAuthGenericRecordInfo().Disabled)
|
|
continue;
|
|
|
|
string nameServerHost = (nsRecord.RDATA as DnsNSRecordData).NameServer;
|
|
|
|
if (primaryNameServer.Equals(nameServerHost, StringComparison.OrdinalIgnoreCase))
|
|
continue; //skip primary name server
|
|
|
|
if (onlyFailedNameServers)
|
|
{
|
|
lock (_notifyFailed)
|
|
{
|
|
if (!_notifyFailed.Contains(nameServerHost))
|
|
continue;
|
|
}
|
|
}
|
|
|
|
notifiedNameServers.Add(nameServerHost);
|
|
|
|
List<NameServerAddress> nameServers = new List<NameServerAddress>(2);
|
|
await ResolveNameServerAddressesAsync(nsRecord, nameServers);
|
|
|
|
if (nameServers.Count > 0)
|
|
{
|
|
tasks.Add(NotifyNameServerAsync(nameServerHost, nameServers));
|
|
}
|
|
else
|
|
{
|
|
lock (_notifyFailed)
|
|
{
|
|
if (!_notifyFailed.Contains(nameServerHost))
|
|
_notifyFailed.Add(nameServerHost);
|
|
}
|
|
|
|
_dnsServer.LogManager?.Write("DNS Server failed to notify name server '" + nameServerHost + "' due to failure in resolving its IP address for zone: " + ToString());
|
|
}
|
|
}
|
|
|
|
await Task.WhenAll(tasks);
|
|
}
|
|
|
|
Task NotifySpecifiedNameServersAsync(bool onlyFailedNameServers)
|
|
{
|
|
IReadOnlyCollection<IPAddress> specifiedNameServers = apexZone._notifyNameServers;
|
|
if (specifiedNameServers is not null)
|
|
return NotifyNameServersAsync(specifiedNameServers, onlyFailedNameServers);
|
|
|
|
return Task.CompletedTask;
|
|
}
|
|
|
|
Task NotifySecondaryCatalogNameServersAsync(bool onlyFailedNameServers)
|
|
{
|
|
IReadOnlyCollection<IPAddress> secondaryCatalogNameServers = apexZone._notifySecondaryCatalogNameServers;
|
|
if (secondaryCatalogNameServers is not null)
|
|
return NotifyNameServersAsync(secondaryCatalogNameServers, onlyFailedNameServers);
|
|
|
|
return Task.CompletedTask;
|
|
}
|
|
|
|
async Task NotifyNameServersAsync(IReadOnlyCollection<IPAddress> nameServerIpAddresses, bool onlyFailedNameServers)
|
|
{
|
|
List<Task> tasks = new List<Task>();
|
|
|
|
foreach (IPAddress nameServerIpAddress in nameServerIpAddresses)
|
|
{
|
|
string nameServerHost = nameServerIpAddress.ToString();
|
|
|
|
if (onlyFailedNameServers)
|
|
{
|
|
lock (_notifyFailed)
|
|
{
|
|
if (!_notifyFailed.Contains(nameServerHost))
|
|
continue;
|
|
}
|
|
}
|
|
|
|
notifiedNameServers.Add(nameServerHost);
|
|
|
|
tasks.Add(NotifyNameServerAsync(nameServerHost, [new NameServerAddress(nameServerIpAddress)]));
|
|
}
|
|
|
|
await Task.WhenAll(tasks);
|
|
}
|
|
|
|
try
|
|
{
|
|
switch (apexZone._notify)
|
|
{
|
|
case AuthZoneNotify.ZoneNameServers:
|
|
await NotifyZoneNameServersAsync(!_notifyTimerTriggered);
|
|
break;
|
|
|
|
case AuthZoneNotify.SpecifiedNameServers:
|
|
await NotifySpecifiedNameServersAsync(!_notifyTimerTriggered);
|
|
break;
|
|
|
|
case AuthZoneNotify.BothZoneAndSpecifiedNameServers:
|
|
Task t1 = NotifyZoneNameServersAsync(!_notifyTimerTriggered);
|
|
Task t2 = NotifySpecifiedNameServersAsync(!_notifyTimerTriggered);
|
|
|
|
await Task.WhenAll(t1, t2);
|
|
break;
|
|
|
|
case AuthZoneNotify.SeparateNameServersForCatalogAndMemberZones:
|
|
if (this is CatalogZone)
|
|
await NotifySecondaryCatalogNameServersAsync(!_notifyTimerTriggered);
|
|
else
|
|
await NotifySpecifiedNameServersAsync(!_notifyTimerTriggered);
|
|
|
|
break;
|
|
}
|
|
|
|
//remove non-existent name servers from notify failed list
|
|
lock (_notifyFailed)
|
|
{
|
|
if (_notifyFailed.Count > 0)
|
|
{
|
|
List<string> toRemove = new List<string>();
|
|
|
|
foreach (string failedNameServer in _notifyFailed)
|
|
{
|
|
if (!notifiedNameServers.Contains(failedNameServer))
|
|
toRemove.Add(failedNameServer);
|
|
}
|
|
|
|
foreach (string failedNameServer in toRemove)
|
|
_notifyFailed.Remove(failedNameServer);
|
|
|
|
if (_notifyFailed.Count > 0)
|
|
{
|
|
//set timer to notify failed name servers again
|
|
int retryInterval = (int)((_entries[DnsResourceRecordType.SOA][0].RDATA as DnsSOARecordData).Retry * 1000);
|
|
_notifyTimer.Change(retryInterval, Timeout.Infinite);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
_dnsServer.LogManager?.Write(ex);
|
|
}
|
|
finally
|
|
{
|
|
_notifyTimerTriggered = false;
|
|
}
|
|
}
|
|
|
|
private async Task NotifyNameServerAsync(string nameServerHost, IReadOnlyList<NameServerAddress> nameServers)
|
|
{
|
|
//use notify list to prevent multiple threads from notifying the same name server
|
|
lock (_notifyList)
|
|
{
|
|
if (_notifyList.Contains(nameServerHost))
|
|
return; //already notifying the name server in another thread
|
|
|
|
_notifyList.Add(nameServerHost);
|
|
}
|
|
|
|
try
|
|
{
|
|
DnsClient client = new DnsClient(nameServers);
|
|
|
|
client.Proxy = _dnsServer.Proxy;
|
|
client.Timeout = NOTIFY_TIMEOUT;
|
|
client.Retries = NOTIFY_RETRIES;
|
|
|
|
DnsDatagram notifyRequest = new DnsDatagram(0, false, DnsOpcode.Notify, true, false, false, false, false, false, DnsResponseCode.NoError, new DnsQuestionRecord[] { new DnsQuestionRecord(_name, DnsResourceRecordType.SOA, DnsClass.IN) }, _entries[DnsResourceRecordType.SOA]);
|
|
DnsDatagram response = await client.RawResolveAsync(notifyRequest);
|
|
|
|
switch (response.RCODE)
|
|
{
|
|
case DnsResponseCode.NoError:
|
|
case DnsResponseCode.NotImplemented:
|
|
{
|
|
//transaction complete
|
|
lock (_notifyFailed)
|
|
{
|
|
_notifyFailed.Remove(nameServerHost);
|
|
}
|
|
|
|
_dnsServer.LogManager?.Write("DNS Server successfully notified name server '" + nameServerHost + "' for zone: " + ToString());
|
|
}
|
|
break;
|
|
|
|
default:
|
|
{
|
|
//transaction failed
|
|
lock (_notifyFailed)
|
|
{
|
|
if (!_notifyFailed.Contains(nameServerHost))
|
|
_notifyFailed.Add(nameServerHost);
|
|
}
|
|
|
|
_dnsServer.LogManager?.Write("DNS Server failed to notify name server '" + nameServerHost + "' (RCODE=" + response.RCODE.ToString() + ") for zone: " + ToString());
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
lock (_notifyFailed)
|
|
{
|
|
if (!_notifyFailed.Contains(nameServerHost))
|
|
_notifyFailed.Add(nameServerHost);
|
|
}
|
|
|
|
_dnsServer.LogManager?.Write("DNS Server failed to notify name server '" + nameServerHost + "' for zone: " + ToString() + "\r\n" + ex.ToString());
|
|
}
|
|
finally
|
|
{
|
|
lock (_notifyList)
|
|
{
|
|
_notifyList.Remove(nameServerHost);
|
|
}
|
|
}
|
|
}
|
|
|
|
internal void RemoveFromNotifyFailedList(NameServerAddress allowedZoneNameServer, IPAddress allowedIPAddress)
|
|
{
|
|
if (_notifyFailed is null)
|
|
return;
|
|
|
|
lock (_notifyFailed)
|
|
{
|
|
if (_notifyFailed.Count == 0)
|
|
return;
|
|
|
|
if ((allowedZoneNameServer is not null) && (allowedZoneNameServer.DomainEndPoint is not null))
|
|
_notifyFailed.Remove(allowedZoneNameServer.DomainEndPoint.Address);
|
|
|
|
_notifyFailed.Remove(allowedIPAddress.ToString());
|
|
}
|
|
}
|
|
|
|
public void TriggerNotify()
|
|
{
|
|
if (Disabled)
|
|
return;
|
|
|
|
ApexZone apexZone = this;
|
|
|
|
if ((apexZone.CatalogZone is not null) && !apexZone.OverrideCatalogNotify)
|
|
apexZone = apexZone.CatalogZone;
|
|
|
|
if (apexZone._notify == AuthZoneNotify.None)
|
|
{
|
|
if (_notifyFailed is not null)
|
|
{
|
|
lock (_notifyFailed)
|
|
{
|
|
_notifyFailed.Clear();
|
|
}
|
|
}
|
|
|
|
return;
|
|
}
|
|
|
|
if (_notifyTimerTriggered)
|
|
return;
|
|
|
|
if (_disposed)
|
|
return;
|
|
|
|
if (_notifyTimer is null)
|
|
return;
|
|
|
|
_notifyTimer.Change(NOTIFY_TIMER_INTERVAL, Timeout.Infinite);
|
|
_notifyTimerTriggered = true;
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region record expiry
|
|
|
|
protected void InitRecordExpiry()
|
|
{
|
|
_recordExpiryTimer = new Timer(RecordExpiryTimerCallback, null, Timeout.Infinite, Timeout.Infinite);
|
|
}
|
|
|
|
private uint GetMinRecordExpiryTtl(uint minExpiryTtl)
|
|
{
|
|
if (!_recordExpiryTimerRunning)
|
|
return Math.Min(minExpiryTtl, uint.MaxValue / 1000);
|
|
|
|
uint elapsedSeconds = Convert.ToUInt32((DateTime.UtcNow - _recordExpiryTimerStartedOn).TotalSeconds);
|
|
if (elapsedSeconds >= _recordExpiryTimerTtl)
|
|
return 0u;
|
|
|
|
uint pendingExpiryTtl = _recordExpiryTimerTtl - elapsedSeconds;
|
|
|
|
return Math.Min(Math.Min(pendingExpiryTtl, minExpiryTtl), uint.MaxValue / 1000);
|
|
}
|
|
|
|
public void StartRecordExpiryTimer(uint minExpiryTtl)
|
|
{
|
|
lock (_recordExpiryTimerLock)
|
|
{
|
|
if (_recordExpiryTimer is not null)
|
|
{
|
|
uint minTtl = GetMinRecordExpiryTtl(minExpiryTtl);
|
|
|
|
_recordExpiryTimer.Change(minTtl * 1000, Timeout.Infinite);
|
|
_recordExpiryTimerStartedOn = DateTime.UtcNow;
|
|
_recordExpiryTimerTtl = minTtl;
|
|
_recordExpiryTimerRunning = true;
|
|
}
|
|
}
|
|
}
|
|
|
|
private void RecordExpiryTimerCallback(object state)
|
|
{
|
|
_recordExpiryTimerRunning = false;
|
|
uint minExpiryTtl = 0u;
|
|
|
|
try
|
|
{
|
|
IReadOnlyList<AuthZone> authZones = _dnsServer.AuthZoneManager.GetApexZoneWithSubDomainZones(_name);
|
|
bool recordsDeleted = false;
|
|
|
|
foreach (AuthZone authZone in authZones)
|
|
{
|
|
foreach (KeyValuePair<DnsResourceRecordType, IReadOnlyList<DnsResourceRecord>> entry in authZone.Entries)
|
|
{
|
|
foreach (DnsResourceRecord record in entry.Value)
|
|
{
|
|
GenericRecordInfo recordInfo = record.GetAuthGenericRecordInfo();
|
|
if (recordInfo.ExpiryTtl > 0u)
|
|
{
|
|
uint pendingExpiryTtl = recordInfo.GetPendingExpiryTtl();
|
|
if (pendingExpiryTtl == 0u)
|
|
{
|
|
if (_dnsServer.AuthZoneManager.DeleteRecord(_name, record))
|
|
recordsDeleted = true;
|
|
}
|
|
else
|
|
{
|
|
if (minExpiryTtl == 0u)
|
|
minExpiryTtl = pendingExpiryTtl;
|
|
else
|
|
minExpiryTtl = Math.Min(minExpiryTtl, pendingExpiryTtl);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if (recordsDeleted)
|
|
_dnsServer.AuthZoneManager.SaveZoneFile(_name);
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
_dnsServer.LogManager?.Write(ex);
|
|
}
|
|
finally
|
|
{
|
|
if (minExpiryTtl > 0u)
|
|
StartRecordExpiryTimer(minExpiryTtl);
|
|
}
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region internal
|
|
|
|
internal virtual void UpdateDnssecStatus()
|
|
{
|
|
if (!_entries.ContainsKey(DnsResourceRecordType.DNSKEY))
|
|
_dnssecStatus = AuthZoneDnssecStatus.Unsigned;
|
|
else if (_entries.ContainsKey(DnsResourceRecordType.NSEC3PARAM))
|
|
_dnssecStatus = AuthZoneDnssecStatus.SignedWithNSEC3;
|
|
else
|
|
_dnssecStatus = AuthZoneDnssecStatus.SignedWithNSEC;
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region versioning
|
|
|
|
internal virtual void CommitAndIncrementSerial(IReadOnlyList<DnsResourceRecord> deletedRecords = null, IReadOnlyList<DnsResourceRecord> addedRecords = null)
|
|
{
|
|
_lastModified = DateTime.UtcNow;
|
|
|
|
if (addedRecords is not null)
|
|
{
|
|
uint minExpiryTtl = 0u;
|
|
|
|
foreach (DnsResourceRecord addedRecord in addedRecords)
|
|
{
|
|
uint expiryTtl = addedRecord.GetAuthGenericRecordInfo().ExpiryTtl;
|
|
if (expiryTtl > 0u)
|
|
{
|
|
if (minExpiryTtl == 0u)
|
|
minExpiryTtl = expiryTtl;
|
|
else
|
|
minExpiryTtl = Math.Min(minExpiryTtl, expiryTtl);
|
|
}
|
|
}
|
|
|
|
if (minExpiryTtl > 0u)
|
|
StartRecordExpiryTimer(minExpiryTtl);
|
|
}
|
|
|
|
lock (_zoneHistory)
|
|
{
|
|
DnsResourceRecord oldSoaRecord = _entries[DnsResourceRecordType.SOA][0];
|
|
DnsResourceRecord newSoaRecord;
|
|
{
|
|
DnsSOARecordData oldSoa = oldSoaRecord.RDATA as DnsSOARecordData;
|
|
|
|
if ((addedRecords is not null) && (addedRecords.Count == 1) && (addedRecords[0].Type == DnsResourceRecordType.SOA))
|
|
{
|
|
DnsResourceRecord addSoaRecord = addedRecords[0];
|
|
DnsSOARecordData addSoa = addSoaRecord.RDATA as DnsSOARecordData;
|
|
|
|
uint serial = GetNewSerial(oldSoa.Serial, addSoa.Serial, addSoaRecord.GetAuthSOARecordInfo().UseSoaSerialDateScheme);
|
|
|
|
newSoaRecord = new DnsResourceRecord(_name, DnsResourceRecordType.SOA, DnsClass.IN, addSoaRecord.TTL, new DnsSOARecordData(addSoa.PrimaryNameServer, addSoa.ResponsiblePerson, serial, addSoa.Refresh, addSoa.Retry, addSoa.Expire, addSoa.Minimum)) { Tag = addSoaRecord.Tag };
|
|
addedRecords = null;
|
|
}
|
|
else
|
|
{
|
|
uint serial = GetNewSerial(oldSoa.Serial, 0, oldSoaRecord.GetAuthSOARecordInfo().UseSoaSerialDateScheme);
|
|
|
|
newSoaRecord = new DnsResourceRecord(_name, DnsResourceRecordType.SOA, DnsClass.IN, oldSoaRecord.TTL, new DnsSOARecordData(oldSoa.PrimaryNameServer, oldSoa.ResponsiblePerson, serial, oldSoa.Refresh, oldSoa.Retry, oldSoa.Expire, oldSoa.Minimum)) { Tag = oldSoaRecord.Tag };
|
|
}
|
|
}
|
|
|
|
DnsResourceRecord[] newSoaRecords = [newSoaRecord];
|
|
|
|
//update SOA
|
|
_entries[DnsResourceRecordType.SOA] = newSoaRecords;
|
|
|
|
IReadOnlyList<DnsResourceRecord> newRRSigRecords = null;
|
|
IReadOnlyList<DnsResourceRecord> deletedRRSigRecords = null;
|
|
|
|
if (_dnssecStatus != AuthZoneDnssecStatus.Unsigned)
|
|
{
|
|
//sign SOA and update RRSig
|
|
newRRSigRecords = SignRRSet(newSoaRecords);
|
|
AddOrUpdateRRSigRecords(newRRSigRecords, out deletedRRSigRecords);
|
|
}
|
|
|
|
//remove RR info from old SOA to allow creating new history RR info for setting DeletedOn
|
|
oldSoaRecord.Tag = null;
|
|
|
|
//start commit
|
|
oldSoaRecord.GetAuthHistoryRecordInfo().DeletedOn = DateTime.UtcNow;
|
|
|
|
//write removed
|
|
_zoneHistory.Add(oldSoaRecord);
|
|
|
|
if (deletedRecords is not null)
|
|
{
|
|
foreach (DnsResourceRecord deletedRecord in deletedRecords)
|
|
{
|
|
if (deletedRecord.GetAuthGenericRecordInfo().Disabled)
|
|
continue;
|
|
|
|
_zoneHistory.Add(deletedRecord);
|
|
|
|
if (deletedRecord.Type == DnsResourceRecordType.NS)
|
|
{
|
|
IReadOnlyList<DnsResourceRecord> glueRecords = deletedRecord.GetAuthNSRecordInfo().GlueRecords;
|
|
if (glueRecords is not null)
|
|
_zoneHistory.AddRange(glueRecords);
|
|
}
|
|
}
|
|
}
|
|
|
|
if (deletedRRSigRecords is not null)
|
|
_zoneHistory.AddRange(deletedRRSigRecords);
|
|
|
|
//write added
|
|
_zoneHistory.Add(newSoaRecord);
|
|
|
|
if (addedRecords is not null)
|
|
{
|
|
foreach (DnsResourceRecord addedRecord in addedRecords)
|
|
{
|
|
if (addedRecord.GetAuthGenericRecordInfo().Disabled)
|
|
continue;
|
|
|
|
_zoneHistory.Add(addedRecord);
|
|
|
|
if (addedRecord.Type == DnsResourceRecordType.NS)
|
|
{
|
|
IReadOnlyList<DnsResourceRecord> glueRecords = addedRecord.GetAuthNSRecordInfo().GlueRecords;
|
|
if (glueRecords is not null)
|
|
_zoneHistory.AddRange(glueRecords);
|
|
}
|
|
}
|
|
}
|
|
|
|
if (newRRSigRecords is not null)
|
|
_zoneHistory.AddRange(newRRSigRecords);
|
|
|
|
//end commit
|
|
|
|
CleanupHistory();
|
|
}
|
|
}
|
|
|
|
protected static uint GetNewSerial(uint oldSerial, uint updateSerial, bool useSoaSerialDateScheme)
|
|
{
|
|
if (useSoaSerialDateScheme)
|
|
{
|
|
string strOldSerial = oldSerial.ToString();
|
|
string strOldSerialDate = null;
|
|
byte counter = 0;
|
|
|
|
if (strOldSerial.Length == 10)
|
|
{
|
|
//parse old serial
|
|
strOldSerialDate = strOldSerial.Substring(0, 8);
|
|
counter = byte.Parse(strOldSerial.Substring(8));
|
|
}
|
|
|
|
string strSerialDate = DateTime.UtcNow.ToString("yyyyMMdd");
|
|
|
|
if (strOldSerialDate is null)
|
|
{
|
|
//transitioning to date scheme
|
|
return uint.Parse(strSerialDate + counter.ToString().PadLeft(2, '0'));
|
|
}
|
|
else if (strSerialDate.Equals(strOldSerialDate))
|
|
{
|
|
//same date
|
|
if (counter < 99)
|
|
{
|
|
counter++;
|
|
return uint.Parse(strSerialDate + counter.ToString().PadLeft(2, '0'));
|
|
}
|
|
else
|
|
{
|
|
//more than 100 increments
|
|
return uint.Parse(strSerialDate + counter.ToString().PadLeft(2, '0')) + 1;
|
|
}
|
|
}
|
|
else if (uint.Parse(strSerialDate) > uint.Parse(strOldSerialDate))
|
|
{
|
|
//later date
|
|
return uint.Parse(strSerialDate + "00");
|
|
}
|
|
}
|
|
|
|
//default
|
|
uint serial = oldSerial;
|
|
|
|
if (updateSerial > serial)
|
|
serial = updateSerial;
|
|
else if (serial < uint.MaxValue)
|
|
serial++;
|
|
else
|
|
serial = 1;
|
|
|
|
return serial;
|
|
}
|
|
|
|
internal void SetSoaSerial(uint newSerial)
|
|
{
|
|
lock (_zoneHistory)
|
|
{
|
|
DnsResourceRecord oldSoaRecord = _entries[DnsResourceRecordType.SOA][0];
|
|
DnsSOARecordData oldSoa = oldSoaRecord.RDATA as DnsSOARecordData;
|
|
|
|
DnsResourceRecord newSoaRecord = new DnsResourceRecord(_name, DnsResourceRecordType.SOA, DnsClass.IN, oldSoaRecord.TTL, new DnsSOARecordData(oldSoa.PrimaryNameServer, oldSoa.ResponsiblePerson, newSerial, oldSoa.Refresh, oldSoa.Retry, oldSoa.Expire, oldSoa.Minimum)) { Tag = oldSoaRecord.Tag };
|
|
DnsResourceRecord[] newSoaRecords = [newSoaRecord];
|
|
|
|
//update SOA
|
|
_entries[DnsResourceRecordType.SOA] = newSoaRecords;
|
|
|
|
//clear history
|
|
_zoneHistory.Clear();
|
|
}
|
|
}
|
|
|
|
public IReadOnlyList<DnsResourceRecord> GetZoneHistory()
|
|
{
|
|
lock (_zoneHistory)
|
|
{
|
|
return _zoneHistory.ToArray();
|
|
}
|
|
}
|
|
|
|
protected void CleanupHistory()
|
|
{
|
|
DnsSOARecordData soa = _entries[DnsResourceRecordType.SOA][0].RDATA as DnsSOARecordData;
|
|
DateTime expiry = DateTime.UtcNow.AddSeconds(-soa.Expire);
|
|
int index = 0;
|
|
|
|
while (index < _zoneHistory.Count)
|
|
{
|
|
//check difference sequence
|
|
if (_zoneHistory[index].GetAuthHistoryRecordInfo().DeletedOn > expiry)
|
|
break; //found record to keep
|
|
|
|
//skip to next difference sequence
|
|
index++;
|
|
int soaCount = 1;
|
|
|
|
while (index < _zoneHistory.Count)
|
|
{
|
|
if (_zoneHistory[index].Type == DnsResourceRecordType.SOA)
|
|
{
|
|
soaCount++;
|
|
|
|
if (soaCount == 3)
|
|
break;
|
|
}
|
|
|
|
index++;
|
|
}
|
|
}
|
|
|
|
if (index == _zoneHistory.Count)
|
|
{
|
|
//delete entire history
|
|
_zoneHistory.Clear();
|
|
return;
|
|
}
|
|
|
|
//remove expired records
|
|
_zoneHistory.RemoveRange(0, index);
|
|
}
|
|
|
|
protected void CommitZoneHistory(IReadOnlyList<DnsResourceRecord> historyRecords)
|
|
{
|
|
lock (_zoneHistory)
|
|
{
|
|
historyRecords[0].GetAuthHistoryRecordInfo().DeletedOn = DateTime.UtcNow;
|
|
|
|
//write history
|
|
_zoneHistory.AddRange(historyRecords);
|
|
|
|
CleanupHistory();
|
|
}
|
|
}
|
|
|
|
protected void ClearZoneHistory()
|
|
{
|
|
lock (_zoneHistory)
|
|
{
|
|
_zoneHistory.Clear();
|
|
}
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region catalog zone
|
|
|
|
private IReadOnlyCollection<NetworkAccessControl> GetQueryAccessACL()
|
|
{
|
|
switch (_queryAccess)
|
|
{
|
|
case AuthZoneQueryAccess.Allow:
|
|
return [
|
|
new NetworkAccessControl(IPAddress.Any, 0),
|
|
new NetworkAccessControl(IPAddress.IPv6Any, 0)
|
|
];
|
|
|
|
case AuthZoneQueryAccess.AllowOnlyPrivateNetworks:
|
|
return [
|
|
new NetworkAccessControl(IPAddress.Parse("127.0.0.0"), 8),
|
|
new NetworkAccessControl(IPAddress.Parse("10.0.0.0"), 8),
|
|
new NetworkAccessControl(IPAddress.Parse("100.64.0.0"), 10),
|
|
new NetworkAccessControl(IPAddress.Parse("169.254.0.0"), 16),
|
|
new NetworkAccessControl(IPAddress.Parse("172.16.0.0"), 12),
|
|
new NetworkAccessControl(IPAddress.Parse("192.168.0.0"), 16),
|
|
new NetworkAccessControl(IPAddress.Parse("2000::"), 3, true),
|
|
new NetworkAccessControl(IPAddress.IPv6Any, 0)
|
|
];
|
|
|
|
case AuthZoneQueryAccess.AllowOnlyZoneNameServers:
|
|
return [
|
|
new NetworkAccessControl(IPAddress.Parse("224.0.0.0"), 32)
|
|
];
|
|
|
|
case AuthZoneQueryAccess.UseSpecifiedNetworkACL:
|
|
return _queryAccessNetworkACL;
|
|
|
|
case AuthZoneQueryAccess.AllowZoneNameServersAndUseSpecifiedNetworkACL:
|
|
if (_queryAccessNetworkACL is null)
|
|
{
|
|
return [
|
|
new NetworkAccessControl(IPAddress.Parse("224.0.0.0"), 32)
|
|
];
|
|
}
|
|
|
|
return [
|
|
new NetworkAccessControl(IPAddress.Parse("224.0.0.0"), 32),
|
|
.._queryAccessNetworkACL
|
|
];
|
|
|
|
case AuthZoneQueryAccess.Deny:
|
|
default:
|
|
return [
|
|
new NetworkAccessControl(IPAddress.Parse("127.0.0.0"), 8),
|
|
new NetworkAccessControl(IPAddress.Parse("::1"), 128)
|
|
];
|
|
}
|
|
}
|
|
|
|
private IReadOnlyCollection<NetworkAccessControl> GetZoneTranferACL()
|
|
{
|
|
switch (_zoneTransfer)
|
|
{
|
|
case AuthZoneTransfer.Allow:
|
|
return [
|
|
new NetworkAccessControl(IPAddress.Any, 0),
|
|
new NetworkAccessControl(IPAddress.IPv6Any, 0)
|
|
];
|
|
|
|
case AuthZoneTransfer.AllowOnlyZoneNameServers:
|
|
return [
|
|
new NetworkAccessControl(IPAddress.Parse("224.0.0.0"), 32)
|
|
];
|
|
|
|
case AuthZoneTransfer.UseSpecifiedNetworkACL:
|
|
return _zoneTransferNetworkACL;
|
|
|
|
case AuthZoneTransfer.AllowZoneNameServersAndUseSpecifiedNetworkACL:
|
|
if (_zoneTransferNetworkACL is null)
|
|
{
|
|
return [
|
|
new NetworkAccessControl(IPAddress.Parse("224.0.0.0"), 32)
|
|
];
|
|
}
|
|
|
|
return [
|
|
new NetworkAccessControl(IPAddress.Parse("224.0.0.0"), 32),
|
|
.._zoneTransferNetworkACL
|
|
];
|
|
|
|
case AuthZoneTransfer.Deny:
|
|
default:
|
|
return [
|
|
new NetworkAccessControl(IPAddress.Any, 0, true),
|
|
new NetworkAccessControl(IPAddress.IPv6Any, 0, true)
|
|
];
|
|
}
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region public
|
|
|
|
public uint GetZoneSoaMinimum()
|
|
{
|
|
return (_entries[DnsResourceRecordType.SOA][0].RDATA as DnsSOARecordData).Minimum;
|
|
}
|
|
|
|
public uint GetZoneSoaExpire()
|
|
{
|
|
return (_entries[DnsResourceRecordType.SOA][0].RDATA as DnsSOARecordData).Expire;
|
|
}
|
|
|
|
public uint GetZoneSoaSerial()
|
|
{
|
|
return (_entries[DnsResourceRecordType.SOA][0].RDATA as DnsSOARecordData).Serial;
|
|
}
|
|
|
|
public abstract string GetZoneTypeName();
|
|
|
|
public override string ToString()
|
|
{
|
|
return _name.Length == 0 ? "<root>" : _name;
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region name server address resolution
|
|
|
|
public async Task<IReadOnlyList<NameServerAddress>> GetResolvedPrimaryNameServerAddressesAsync()
|
|
{
|
|
IReadOnlyList<NameServerAddress> primaryNameServers;
|
|
|
|
if (this is SecondaryZone secondary)
|
|
primaryNameServers = secondary.PrimaryNameServerAddresses;
|
|
else if (this is StubZone stub)
|
|
primaryNameServers = stub.PrimaryNameServerAddresses;
|
|
else
|
|
primaryNameServers = null;
|
|
|
|
if (primaryNameServers is not null)
|
|
return await GetResolvedNameServerAddressesAsync(primaryNameServers);
|
|
|
|
DnsResourceRecord soaRecord = _entries[DnsResourceRecordType.SOA][0];
|
|
string primaryNameServer = (soaRecord.RDATA as DnsSOARecordData).PrimaryNameServer;
|
|
IReadOnlyList<DnsResourceRecord> nsRecords = GetRecords(DnsResourceRecordType.NS); //stub zone has no authority so cant use QueryRecords
|
|
|
|
List<NameServerAddress> nameServers = new List<NameServerAddress>(nsRecords.Count * 2);
|
|
|
|
foreach (DnsResourceRecord nsRecord in nsRecords)
|
|
{
|
|
if (nsRecord.GetAuthGenericRecordInfo().Disabled)
|
|
continue;
|
|
|
|
if (primaryNameServer.Equals((nsRecord.RDATA as DnsNSRecordData).NameServer, StringComparison.OrdinalIgnoreCase))
|
|
{
|
|
//found primary NS
|
|
await ResolveNameServerAddressesAsync(nsRecord, nameServers);
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (nameServers.Count < 1)
|
|
await ResolveNameServerAddressesAsync(primaryNameServer, 53, DnsTransportProtocol.Udp, nameServers);
|
|
|
|
return nameServers;
|
|
}
|
|
|
|
public async Task<IReadOnlyList<NameServerAddress>> GetResolvedSecondaryNameServerAddressesAsync()
|
|
{
|
|
string primaryNameServer = (_entries[DnsResourceRecordType.SOA][0].RDATA as DnsSOARecordData).PrimaryNameServer;
|
|
IReadOnlyList<DnsResourceRecord> nsRecords = GetRecords(DnsResourceRecordType.NS); //stub zone has no authority so cant use QueryRecords
|
|
|
|
List<NameServerAddress> nameServers = new List<NameServerAddress>(nsRecords.Count * 2);
|
|
|
|
foreach (DnsResourceRecord nsRecord in nsRecords)
|
|
{
|
|
if (nsRecord.GetAuthGenericRecordInfo().Disabled)
|
|
continue;
|
|
|
|
if (primaryNameServer.Equals((nsRecord.RDATA as DnsNSRecordData).NameServer, StringComparison.OrdinalIgnoreCase))
|
|
continue; //skip primary name server
|
|
|
|
await ResolveNameServerAddressesAsync(nsRecord, nameServers);
|
|
}
|
|
|
|
return nameServers;
|
|
}
|
|
|
|
public async Task<IReadOnlyList<NameServerAddress>> GetAllResolvedNameServerAddressesAsync()
|
|
{
|
|
IReadOnlyList<DnsResourceRecord> nsRecords = GetRecords(DnsResourceRecordType.NS); //stub zone has no authority so cant use QueryRecords
|
|
|
|
List<NameServerAddress> nameServers = new List<NameServerAddress>(nsRecords.Count * 2);
|
|
|
|
foreach (DnsResourceRecord nsRecord in nsRecords)
|
|
{
|
|
if (nsRecord.GetAuthGenericRecordInfo().Disabled)
|
|
continue;
|
|
|
|
await ResolveNameServerAddressesAsync(nsRecord, nameServers);
|
|
}
|
|
|
|
return nameServers;
|
|
}
|
|
|
|
public async Task<IReadOnlyList<NameServerAddress>> GetResolvedNameServerAddressesAsync(IReadOnlyList<NameServerAddress> nameServers)
|
|
{
|
|
List<NameServerAddress> resolvedNameServers = new List<NameServerAddress>(nameServers.Count * 2);
|
|
|
|
foreach (NameServerAddress nameServer in nameServers)
|
|
{
|
|
if (nameServer.IsIPEndPointStale)
|
|
await ResolveNameServerAddressesAsync(nameServer.Host, nameServer.Port, nameServer.Protocol, resolvedNameServers);
|
|
else
|
|
resolvedNameServers.Add(nameServer);
|
|
}
|
|
|
|
return resolvedNameServers;
|
|
}
|
|
|
|
private async Task ResolveNameServerAddressesAsync(string nsDomain, int port, DnsTransportProtocol protocol, List<NameServerAddress> outNameServers)
|
|
{
|
|
try
|
|
{
|
|
DnsDatagram response = await _dnsServer.DirectQueryAsync(new DnsQuestionRecord(nsDomain, DnsResourceRecordType.A, DnsClass.IN));
|
|
if (response.Answer.Count > 0)
|
|
{
|
|
IReadOnlyList<IPAddress> addresses = DnsClient.ParseResponseA(response);
|
|
foreach (IPAddress address in addresses)
|
|
outNameServers.Add(new NameServerAddress(nsDomain, new IPEndPoint(address, port), protocol));
|
|
}
|
|
}
|
|
catch
|
|
{ }
|
|
|
|
if (_dnsServer.PreferIPv6)
|
|
{
|
|
try
|
|
{
|
|
DnsDatagram response = await _dnsServer.DirectQueryAsync(new DnsQuestionRecord(nsDomain, DnsResourceRecordType.AAAA, DnsClass.IN));
|
|
if (response.Answer.Count > 0)
|
|
{
|
|
IReadOnlyList<IPAddress> addresses = DnsClient.ParseResponseAAAA(response);
|
|
foreach (IPAddress address in addresses)
|
|
outNameServers.Add(new NameServerAddress(nsDomain, new IPEndPoint(address, port), protocol));
|
|
}
|
|
}
|
|
catch
|
|
{ }
|
|
}
|
|
}
|
|
|
|
private Task ResolveNameServerAddressesAsync(DnsResourceRecord nsRecord, List<NameServerAddress> outNameServers)
|
|
{
|
|
string nsDomain = (nsRecord.RDATA as DnsNSRecordData).NameServer;
|
|
|
|
IReadOnlyList<DnsResourceRecord> glueRecords = nsRecord.GetAuthNSRecordInfo().GlueRecords;
|
|
if (glueRecords is not null)
|
|
{
|
|
foreach (DnsResourceRecord glueRecord in glueRecords)
|
|
{
|
|
switch (glueRecord.Type)
|
|
{
|
|
case DnsResourceRecordType.A:
|
|
outNameServers.Add(new NameServerAddress(nsDomain, (glueRecord.RDATA as DnsARecordData).Address));
|
|
break;
|
|
|
|
case DnsResourceRecordType.AAAA:
|
|
if (_dnsServer.PreferIPv6)
|
|
outNameServers.Add(new NameServerAddress(nsDomain, (glueRecord.RDATA as DnsAAAARecordData).Address));
|
|
|
|
break;
|
|
}
|
|
}
|
|
|
|
return Task.CompletedTask;
|
|
}
|
|
else
|
|
{
|
|
return ResolveNameServerAddressesAsync(nsDomain, 53, DnsTransportProtocol.Udp, outNameServers);
|
|
}
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region properties
|
|
|
|
public override bool Disabled
|
|
{
|
|
get { return base.Disabled; }
|
|
set
|
|
{
|
|
if (base.Disabled == value)
|
|
return;
|
|
|
|
base.Disabled = value; //set value early to be able to use it for setting catalog properties
|
|
|
|
CatalogZone catalogZone = CatalogZone;
|
|
if (catalogZone is not null)
|
|
{
|
|
if (value)
|
|
{
|
|
//remove catalog zone membership without removing it from zone's options
|
|
catalogZone.RemoveMemberZone(_name);
|
|
_dnsServer.AuthZoneManager.SaveZoneFile(catalogZone._name);
|
|
}
|
|
else
|
|
{
|
|
//add catalog zone membership
|
|
_dnsServer.AuthZoneManager.AddCatalogMemberZone(_catalogZoneName, new AuthZoneInfo(this), true);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
public DateTime LastModified
|
|
{ get { return _lastModified; } }
|
|
|
|
public virtual string CatalogZoneName
|
|
{
|
|
get { return _catalogZoneName; }
|
|
set
|
|
{
|
|
if (string.IsNullOrEmpty(value))
|
|
_catalogZoneName = null;
|
|
else
|
|
_catalogZoneName = value;
|
|
|
|
//reset
|
|
_catalogZone = null;
|
|
_secondaryCatalogZone = null;
|
|
}
|
|
}
|
|
|
|
public virtual bool OverrideCatalogQueryAccess
|
|
{
|
|
get { return _overrideCatalogQueryAccess; }
|
|
set { _overrideCatalogQueryAccess = value; }
|
|
}
|
|
|
|
public virtual bool OverrideCatalogZoneTransfer
|
|
{
|
|
get { return _overrideCatalogZoneTransfer; }
|
|
set { _overrideCatalogZoneTransfer = value; }
|
|
}
|
|
|
|
public virtual bool OverrideCatalogNotify
|
|
{
|
|
get { return _overrideCatalogNotify; }
|
|
set { _overrideCatalogNotify = value; }
|
|
}
|
|
|
|
public virtual AuthZoneQueryAccess QueryAccess
|
|
{
|
|
get { return _queryAccess; }
|
|
set
|
|
{
|
|
_queryAccess = value;
|
|
|
|
//update catalog zone property
|
|
if (this is CatalogZone thisCatalogZone)
|
|
{
|
|
//update global custom property
|
|
thisCatalogZone.SetAllowQueryProperty(GetQueryAccessACL());
|
|
}
|
|
else if (!Disabled && ((this is PrimaryZone) || (this is StubZone) || (this is ForwarderZone)))
|
|
{
|
|
CatalogZone catalogZone = CatalogZone;
|
|
if (catalogZone is not null)
|
|
{
|
|
if (_overrideCatalogQueryAccess)
|
|
catalogZone.SetAllowQueryProperty(GetQueryAccessACL(), _name); //update member zone custom property
|
|
else
|
|
catalogZone.SetAllowQueryProperty(null, _name); //remove member zone custom property
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
public IReadOnlyCollection<NetworkAccessControl> QueryAccessNetworkACL
|
|
{
|
|
get { return _queryAccessNetworkACL; }
|
|
set
|
|
{
|
|
if ((value is null) || (value.Count == 0))
|
|
_queryAccessNetworkACL = null;
|
|
else if (value.Count > byte.MaxValue)
|
|
throw new ArgumentOutOfRangeException(nameof(QueryAccessNetworkACL), "Network ACL cannot have more than 255 entries.");
|
|
else
|
|
_queryAccessNetworkACL = value;
|
|
}
|
|
}
|
|
|
|
public virtual AuthZoneTransfer ZoneTransfer
|
|
{
|
|
get { return _zoneTransfer; }
|
|
set
|
|
{
|
|
_zoneTransfer = value;
|
|
|
|
//update catalog zone property
|
|
if (this is CatalogZone thisCatalogZone)
|
|
{
|
|
//update global custom property
|
|
thisCatalogZone.SetAllowTransferProperty(GetZoneTranferACL());
|
|
}
|
|
else if (!Disabled && (this is PrimaryZone))
|
|
{
|
|
CatalogZone catalogZone = CatalogZone;
|
|
if (catalogZone is not null)
|
|
{
|
|
if (_overrideCatalogZoneTransfer)
|
|
catalogZone.SetAllowTransferProperty(GetZoneTranferACL(), _name); //update member zone custom property
|
|
else
|
|
catalogZone.SetAllowTransferProperty(null, _name); //remove member zone custom property
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
public IReadOnlyCollection<NetworkAccessControl> ZoneTransferNetworkACL
|
|
{
|
|
get { return _zoneTransferNetworkACL; }
|
|
set
|
|
{
|
|
if ((value is null) || (value.Count == 0))
|
|
_zoneTransferNetworkACL = null;
|
|
else if (value.Count > byte.MaxValue)
|
|
throw new ArgumentOutOfRangeException(nameof(ZoneTransferNetworkACL), "Network ACL cannot have more than 255 entries.");
|
|
else
|
|
_zoneTransferNetworkACL = value;
|
|
}
|
|
}
|
|
|
|
public IReadOnlyDictionary<string, object> ZoneTransferTsigKeyNames
|
|
{
|
|
get { return _zoneTransferTsigKeyNames; }
|
|
set
|
|
{
|
|
if ((value is null) || (value.Count == 0))
|
|
_zoneTransferTsigKeyNames = null;
|
|
else if (value.Count > byte.MaxValue)
|
|
throw new ArgumentOutOfRangeException(nameof(ZoneTransferTsigKeyNames), "Zone transfer TSIG key names cannot have more than 255 entries.");
|
|
else
|
|
_zoneTransferTsigKeyNames = value;
|
|
|
|
//update catalog zone property
|
|
if (this is CatalogZone thisCatalogZone)
|
|
{
|
|
//update global custom property
|
|
thisCatalogZone.SetZoneTransferTsigKeyNamesProperty(_zoneTransferTsigKeyNames);
|
|
}
|
|
else if (!Disabled && (this is PrimaryZone))
|
|
{
|
|
CatalogZone catalogZone = CatalogZone;
|
|
if (catalogZone is not null)
|
|
{
|
|
if (_overrideCatalogZoneTransfer)
|
|
catalogZone.SetZoneTransferTsigKeyNamesProperty(_zoneTransferTsigKeyNames, _name); //update member zone custom property
|
|
else
|
|
catalogZone.SetZoneTransferTsigKeyNamesProperty(null, _name); //remove member zone custom property
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
public virtual AuthZoneNotify Notify
|
|
{
|
|
get { return _notify; }
|
|
set
|
|
{
|
|
_notify = value;
|
|
|
|
lock (_notifyFailed)
|
|
{
|
|
_notifyFailed.Clear();
|
|
}
|
|
}
|
|
}
|
|
|
|
public IReadOnlyCollection<IPAddress> NotifyNameServers
|
|
{
|
|
get { return _notifyNameServers; }
|
|
set
|
|
{
|
|
if ((value is null) || (value.Count == 0))
|
|
_notifyNameServers = null;
|
|
else if (value.Count > byte.MaxValue)
|
|
throw new ArgumentOutOfRangeException(nameof(NotifyNameServers), "Name server addresses cannot have more than 255 entries.");
|
|
else
|
|
_notifyNameServers = value;
|
|
|
|
lock (_notifyFailed)
|
|
{
|
|
_notifyFailed.Clear();
|
|
}
|
|
}
|
|
}
|
|
|
|
public IReadOnlyCollection<IPAddress> NotifySecondaryCatalogNameServers
|
|
{
|
|
get { return _notifySecondaryCatalogNameServers; }
|
|
set
|
|
{
|
|
if ((value is null) || (value.Count == 0))
|
|
_notifySecondaryCatalogNameServers = null;
|
|
else if (value.Count > byte.MaxValue)
|
|
throw new ArgumentOutOfRangeException(nameof(NotifySecondaryCatalogNameServers), "Secondary Catalog name server addresses cannot have more than 255 entries.");
|
|
else
|
|
_notifySecondaryCatalogNameServers = value;
|
|
|
|
lock (_notifyFailed)
|
|
{
|
|
_notifyFailed.Clear();
|
|
}
|
|
}
|
|
}
|
|
|
|
public virtual AuthZoneUpdate Update
|
|
{
|
|
get { return _update; }
|
|
set { _update = value; }
|
|
}
|
|
|
|
public IReadOnlyCollection<NetworkAccessControl> UpdateNetworkACL
|
|
{
|
|
get { return _updateNetworkACL; }
|
|
set
|
|
{
|
|
if ((value is null) || (value.Count == 0))
|
|
_updateNetworkACL = null;
|
|
else if (value.Count > byte.MaxValue)
|
|
throw new ArgumentOutOfRangeException(nameof(UpdateNetworkACL), "Network ACL cannot have more than 255 entries.");
|
|
else
|
|
_updateNetworkACL = value;
|
|
}
|
|
}
|
|
|
|
public IReadOnlyDictionary<string, IReadOnlyDictionary<string, IReadOnlyList<DnsResourceRecordType>>> UpdateSecurityPolicies
|
|
{
|
|
get { return _updateSecurityPolicies; }
|
|
set { _updateSecurityPolicies = value; }
|
|
}
|
|
|
|
public AuthZoneDnssecStatus DnssecStatus
|
|
{ get { return _dnssecStatus; } }
|
|
|
|
public string[] NotifyFailed
|
|
{
|
|
get
|
|
{
|
|
if (_notifyFailed is null)
|
|
return Array.Empty<string>();
|
|
|
|
lock (_notifyFailed)
|
|
{
|
|
if (_notifyFailed.Count > 0)
|
|
return _notifyFailed.ToArray();
|
|
|
|
return Array.Empty<string>();
|
|
}
|
|
}
|
|
}
|
|
|
|
public bool SyncFailed
|
|
{ get { return _syncFailed; } }
|
|
|
|
public CatalogZone CatalogZone
|
|
{
|
|
get
|
|
{
|
|
if (_catalogZoneName is null)
|
|
return null;
|
|
|
|
if (_secondaryCatalogZone is not null)
|
|
return null;
|
|
|
|
if (_catalogZone is null)
|
|
{
|
|
if ((this is PrimaryZone) || (this is ForwarderZone))
|
|
{
|
|
ApexZone apexZone = _dnsServer.AuthZoneManager.GetApexZone(_catalogZoneName);
|
|
if (apexZone is CatalogZone catalogZone)
|
|
_catalogZone = catalogZone;
|
|
}
|
|
else if (this is StubZone)
|
|
{
|
|
ApexZone apexZone = _dnsServer.AuthZoneManager.GetApexZone(_catalogZoneName);
|
|
if (apexZone is CatalogZone catalogZone)
|
|
_catalogZone = catalogZone;
|
|
else if (apexZone is SecondaryCatalogZone secondaryCatalogZone)
|
|
_secondaryCatalogZone = secondaryCatalogZone;
|
|
}
|
|
}
|
|
|
|
return _catalogZone;
|
|
}
|
|
}
|
|
|
|
public SecondaryCatalogZone SecondaryCatalogZone
|
|
{
|
|
get
|
|
{
|
|
if (_catalogZoneName is null)
|
|
return null;
|
|
|
|
if (_catalogZone is not null)
|
|
return null;
|
|
|
|
if (_secondaryCatalogZone is null)
|
|
{
|
|
if (this is SecondaryZone)
|
|
{
|
|
ApexZone apexZone = _dnsServer.AuthZoneManager.GetApexZone(_catalogZoneName);
|
|
if (apexZone is SecondaryCatalogZone secondaryCatalogZone)
|
|
_secondaryCatalogZone = secondaryCatalogZone;
|
|
}
|
|
else if (this is StubZone)
|
|
{
|
|
ApexZone apexZone = _dnsServer.AuthZoneManager.GetApexZone(_catalogZoneName);
|
|
if (apexZone is SecondaryCatalogZone secondaryCatalogZone)
|
|
_secondaryCatalogZone = secondaryCatalogZone;
|
|
else if (apexZone is CatalogZone catalogZone)
|
|
_catalogZone = catalogZone;
|
|
}
|
|
}
|
|
|
|
return _secondaryCatalogZone;
|
|
}
|
|
}
|
|
|
|
#endregion
|
|
}
|
|
}
|