Files
DnsServer/DnsServerCore/Dns/Zones/AuthZone.cs

926 lines
36 KiB
C#

/*
Technitium DNS Server
Copyright (C) 2022 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 TechnitiumLibrary;
using TechnitiumLibrary.Net.Dns.ResourceRecords;
namespace DnsServerCore.Dns.Zones
{
abstract class AuthZone : Zone
{
#region variables
protected bool _disabled;
#endregion
#region constructor
protected AuthZone(AuthZoneInfo zoneInfo)
: base(zoneInfo.Name)
{
_disabled = zoneInfo.Disabled;
}
protected AuthZone(string name)
: base(name)
{ }
#endregion
#region private
private IReadOnlyList<DnsResourceRecord> FilterDisabledRecords(DnsResourceRecordType type, IReadOnlyList<DnsResourceRecord> records)
{
if (_disabled)
return Array.Empty<DnsResourceRecord>();
if (records.Count == 1)
{
if (records[0].IsDisabled())
return Array.Empty<DnsResourceRecord>(); //record disabled
//update last used on
records[0].GetRecordInfo().LastUsedOn = DateTime.UtcNow;
return records;
}
List<DnsResourceRecord> newRecords = new List<DnsResourceRecord>(records.Count);
foreach (DnsResourceRecord record in records)
{
if (record.IsDisabled())
continue; //record disabled
newRecords.Add(record);
}
if (newRecords.Count > 1)
{
switch (type)
{
case DnsResourceRecordType.A:
case DnsResourceRecordType.AAAA:
case DnsResourceRecordType.NS:
newRecords.Shuffle(); //shuffle records to allow load balancing
break;
}
}
//update last used on
DateTime utcNow = DateTime.UtcNow;
foreach (DnsResourceRecord record in newRecords)
record.GetRecordInfo().LastUsedOn = utcNow;
return newRecords;
}
private IReadOnlyList<DnsResourceRecord> AppendRRSigTo(IReadOnlyList<DnsResourceRecord> records)
{
IReadOnlyList<DnsResourceRecord> rrsigRecords = GetRecords(DnsResourceRecordType.RRSIG);
if (rrsigRecords.Count == 0)
return records;
DnsResourceRecordType type = records[0].Type;
List<DnsResourceRecord> newRecords = new List<DnsResourceRecord>(records.Count + 2);
newRecords.AddRange(records);
DateTime utcNow = DateTime.UtcNow;
foreach (DnsResourceRecord rrsigRecord in rrsigRecords)
{
if ((rrsigRecord.RDATA as DnsRRSIGRecordData).TypeCovered == type)
{
rrsigRecord.GetRecordInfo().LastUsedOn = utcNow;
newRecords.Add(rrsigRecord);
}
}
return newRecords;
}
#endregion
#region versioning
internal bool TrySetRecords(DnsResourceRecordType type, IReadOnlyList<DnsResourceRecord> records, out IReadOnlyList<DnsResourceRecord> deletedRecords)
{
if (_entries.TryGetValue(type, out IReadOnlyList<DnsResourceRecord> existingRecords))
{
deletedRecords = existingRecords;
return _entries.TryUpdate(type, records, existingRecords);
}
else
{
deletedRecords = Array.Empty<DnsResourceRecord>();
return _entries.TryAdd(type, records);
}
}
internal bool TryDeleteRecord(DnsResourceRecordType type, DnsResourceRecordData rdata, out DnsResourceRecord deletedRecord)
{
if (_entries.TryGetValue(type, out IReadOnlyList<DnsResourceRecord> existingRecords))
{
if (existingRecords.Count == 1)
{
if (rdata.Equals(existingRecords[0].RDATA))
{
if (_entries.TryRemove(type, out IReadOnlyList<DnsResourceRecord> removedRecords))
{
deletedRecord = removedRecords[0];
return true;
}
}
}
else
{
deletedRecord = null;
List<DnsResourceRecord> updatedRecords = new List<DnsResourceRecord>(existingRecords.Count);
foreach (DnsResourceRecord existingRecord in existingRecords)
{
if ((deletedRecord is null) && rdata.Equals(existingRecord.RDATA))
deletedRecord = existingRecord;
else
updatedRecords.Add(existingRecord);
}
if (deletedRecord is null)
return false; //not found
return _entries.TryUpdate(type, updatedRecords, existingRecords);
}
}
deletedRecord = null;
return false;
}
internal bool TryDeleteRecords(DnsResourceRecordType type, IReadOnlyList<DnsResourceRecord> records, out IReadOnlyList<DnsResourceRecord> deletedRecords)
{
if (_entries.TryGetValue(type, out IReadOnlyList<DnsResourceRecord> existingRecords))
{
if (existingRecords.Count == 1)
{
DnsResourceRecord existingRecord = existingRecords[0];
foreach (DnsResourceRecord record in records)
{
if (record.RDATA.Equals(existingRecord.RDATA))
{
if (_entries.TryRemove(type, out IReadOnlyList<DnsResourceRecord> removedRecords))
{
deletedRecords = removedRecords;
return true;
}
}
}
}
else
{
List<DnsResourceRecord> deleted = new List<DnsResourceRecord>(records.Count);
List<DnsResourceRecord> updatedRecords = new List<DnsResourceRecord>(existingRecords.Count);
foreach (DnsResourceRecord existingRecord in existingRecords)
{
bool found = false;
foreach (DnsResourceRecord record in records)
{
if (record.RDATA.Equals(existingRecord.RDATA))
{
found = true;
break;
}
}
if (found)
deleted.Add(existingRecord);
else
updatedRecords.Add(existingRecord);
}
if (deleted.Count > 0)
{
deletedRecords = deleted;
if (updatedRecords.Count > 0)
return _entries.TryUpdate(type, updatedRecords, existingRecords);
return _entries.TryRemove(type, out _);
}
}
}
deletedRecords = null;
return false;
}
internal void AddOrUpdateRRSigRecords(IReadOnlyList<DnsResourceRecord> newRRSigRecords, out IReadOnlyList<DnsResourceRecord> deletedRRSigRecords)
{
IReadOnlyList<DnsResourceRecord> deleted = null;
_entries.AddOrUpdate(DnsResourceRecordType.RRSIG, delegate (DnsResourceRecordType key)
{
deleted = Array.Empty<DnsResourceRecord>();
return newRRSigRecords;
},
delegate (DnsResourceRecordType key, IReadOnlyList<DnsResourceRecord> existingRecords)
{
List<DnsResourceRecord> updatedRecords = new List<DnsResourceRecord>(existingRecords.Count + newRRSigRecords.Count);
List<DnsResourceRecord> deletedRecords = new List<DnsResourceRecord>();
foreach (DnsResourceRecord existingRecord in existingRecords)
{
bool found = false;
DnsRRSIGRecordData existingRRSig = existingRecord.RDATA as DnsRRSIGRecordData;
foreach (DnsResourceRecord newRRSigRecord in newRRSigRecords)
{
DnsRRSIGRecordData newRRSig = newRRSigRecord.RDATA as DnsRRSIGRecordData;
if ((newRRSig.TypeCovered == existingRRSig.TypeCovered) && (newRRSig.KeyTag == existingRRSig.KeyTag))
{
deletedRecords.Add(existingRecord);
found = true;
break;
}
}
if (!found)
updatedRecords.Add(existingRecord);
}
updatedRecords.AddRange(newRRSigRecords);
deleted = deletedRecords;
return updatedRecords;
});
deletedRRSigRecords = deleted;
}
internal void AddRecord(DnsResourceRecord record, out IReadOnlyList<DnsResourceRecord> addedRecords, out IReadOnlyList<DnsResourceRecord> deletedRecords)
{
switch (record.Type)
{
case DnsResourceRecordType.CNAME:
case DnsResourceRecordType.DNAME:
case DnsResourceRecordType.SOA:
throw new InvalidOperationException("Cannot add record: use SetRecords() for " + record.Type.ToString() + " record");
}
List<DnsResourceRecord> added = new List<DnsResourceRecord>();
List<DnsResourceRecord> deleted = new List<DnsResourceRecord>();
addedRecords = added;
deletedRecords = deleted;
_entries.AddOrUpdate(record.Type, delegate (DnsResourceRecordType key)
{
added.Add(record);
return new DnsResourceRecord[] { record };
},
delegate (DnsResourceRecordType key, IReadOnlyList<DnsResourceRecord> existingRecords)
{
foreach (DnsResourceRecord existingRecord in existingRecords)
{
if (record.RDATA.Equals(existingRecord.RDATA))
return existingRecords;
}
List<DnsResourceRecord> updatedRecords = new List<DnsResourceRecord>(existingRecords.Count + 1);
foreach (DnsResourceRecord existingRecord in existingRecords)
{
if (existingRecord.OriginalTtlValue == record.OriginalTtlValue)
{
updatedRecords.Add(existingRecord);
}
else
{
DnsResourceRecord updatedExistingRecord = new DnsResourceRecord(existingRecord.Name, existingRecord.Type, existingRecord.Class, record.OriginalTtlValue, existingRecord.RDATA);
updatedRecords.Add(updatedExistingRecord);
added.Add(updatedExistingRecord);
deleted.Add(existingRecord);
}
}
updatedRecords.Add(record);
added.Add(record);
return updatedRecords;
});
}
#endregion
#region DNSSEC
internal IReadOnlyList<DnsResourceRecord> SignAllRRSets()
{
List<DnsResourceRecord> rrsigRecords = new List<DnsResourceRecord>(_entries.Count);
foreach (KeyValuePair<DnsResourceRecordType, IReadOnlyList<DnsResourceRecord>> entry in _entries)
{
if (entry.Key == DnsResourceRecordType.RRSIG)
continue;
rrsigRecords.AddRange(SignRRSet(entry.Value));
}
return rrsigRecords;
}
internal IReadOnlyList<DnsResourceRecord> RemoveAllDnssecRecords()
{
List<DnsResourceRecord> allRemovedRecords = new List<DnsResourceRecord>();
foreach (KeyValuePair<DnsResourceRecordType, IReadOnlyList<DnsResourceRecord>> entry in _entries)
{
switch (entry.Key)
{
case DnsResourceRecordType.DNSKEY:
case DnsResourceRecordType.RRSIG:
case DnsResourceRecordType.NSEC:
case DnsResourceRecordType.NSEC3PARAM:
case DnsResourceRecordType.NSEC3:
if (_entries.TryRemove(entry.Key, out IReadOnlyList<DnsResourceRecord> removedRecords))
allRemovedRecords.AddRange(removedRecords);
break;
}
}
return allRemovedRecords;
}
internal IReadOnlyList<DnsResourceRecord> RemoveNSecRecordsWithRRSig()
{
List<DnsResourceRecord> allRemovedRecords = new List<DnsResourceRecord>(2);
foreach (KeyValuePair<DnsResourceRecordType, IReadOnlyList<DnsResourceRecord>> entry in _entries)
{
switch (entry.Key)
{
case DnsResourceRecordType.NSEC:
if (_entries.TryRemove(entry.Key, out IReadOnlyList<DnsResourceRecord> removedRecords))
allRemovedRecords.AddRange(removedRecords);
break;
case DnsResourceRecordType.RRSIG:
List<DnsResourceRecord> recordsToRemove = new List<DnsResourceRecord>(1);
foreach (DnsResourceRecord rrsigRecord in entry.Value)
{
DnsRRSIGRecordData rrsig = rrsigRecord.RDATA as DnsRRSIGRecordData;
if (rrsig.TypeCovered == DnsResourceRecordType.NSEC)
recordsToRemove.Add(rrsigRecord);
}
if (recordsToRemove.Count > 0)
{
if (TryDeleteRecords(DnsResourceRecordType.RRSIG, recordsToRemove, out IReadOnlyList<DnsResourceRecord> deletedRecords))
allRemovedRecords.AddRange(deletedRecords);
}
break;
}
}
return allRemovedRecords;
}
internal IReadOnlyList<DnsResourceRecord> RemoveNSec3RecordsWithRRSig()
{
List<DnsResourceRecord> allRemovedRecords = new List<DnsResourceRecord>(2);
foreach (KeyValuePair<DnsResourceRecordType, IReadOnlyList<DnsResourceRecord>> entry in _entries)
{
switch (entry.Key)
{
case DnsResourceRecordType.NSEC3:
case DnsResourceRecordType.NSEC3PARAM:
if (_entries.TryRemove(entry.Key, out IReadOnlyList<DnsResourceRecord> removedRecords))
allRemovedRecords.AddRange(removedRecords);
break;
case DnsResourceRecordType.RRSIG:
List<DnsResourceRecord> recordsToRemove = new List<DnsResourceRecord>(1);
foreach (DnsResourceRecord rrsigRecord in entry.Value)
{
DnsRRSIGRecordData rrsig = rrsigRecord.RDATA as DnsRRSIGRecordData;
switch (rrsig.TypeCovered)
{
case DnsResourceRecordType.NSEC3:
case DnsResourceRecordType.NSEC3PARAM:
recordsToRemove.Add(rrsigRecord);
break;
}
}
if (recordsToRemove.Count > 0)
{
if (TryDeleteRecords(DnsResourceRecordType.RRSIG, recordsToRemove, out IReadOnlyList<DnsResourceRecord> deletedRecords))
allRemovedRecords.AddRange(deletedRecords);
}
break;
}
}
return allRemovedRecords;
}
internal bool HasOnlyNSec3Records()
{
if (!_entries.ContainsKey(DnsResourceRecordType.NSEC3))
return false;
foreach (KeyValuePair<DnsResourceRecordType, IReadOnlyList<DnsResourceRecord>> entry in _entries)
{
switch (entry.Key)
{
case DnsResourceRecordType.NSEC3:
case DnsResourceRecordType.RRSIG:
break;
default:
//found non NSEC3 records
return false;
}
}
return true;
}
internal IReadOnlyList<DnsResourceRecord> RefreshSignatures()
{
if (!_entries.TryGetValue(DnsResourceRecordType.RRSIG, out IReadOnlyList<DnsResourceRecord> rrsigRecords))
throw new InvalidOperationException();
List<DnsResourceRecordType> typesToRefresh = new List<DnsResourceRecordType>();
DateTime utcNow = DateTime.UtcNow;
foreach (DnsResourceRecord rrsigRecord in rrsigRecords)
{
DnsRRSIGRecordData rrsig = rrsigRecord.RDATA as DnsRRSIGRecordData;
uint signatureValidityPeriod = rrsig.SignatureExpirationValue - rrsig.SignatureInceptionValue;
uint refreshPeriod = signatureValidityPeriod / 3;
if (utcNow > DateTime.UnixEpoch.AddSeconds(rrsig.SignatureExpirationValue - refreshPeriod))
typesToRefresh.Add(rrsig.TypeCovered);
}
List<DnsResourceRecord> newRRSigRecords = new List<DnsResourceRecord>(typesToRefresh.Count);
foreach (DnsResourceRecordType type in typesToRefresh)
{
if (_entries.TryGetValue(type, out IReadOnlyList<DnsResourceRecord> records))
newRRSigRecords.AddRange(SignRRSet(records));
}
return newRRSigRecords;
}
internal virtual IReadOnlyList<DnsResourceRecord> SignRRSet(IReadOnlyList<DnsResourceRecord> records)
{
throw new InvalidOperationException();
}
internal IReadOnlyList<DnsResourceRecord> GetUpdatedNSecRRSet(string nextDomainName, uint ttl)
{
List<DnsResourceRecordType> types = new List<DnsResourceRecordType>(_entries.Count);
foreach (KeyValuePair<DnsResourceRecordType, IReadOnlyList<DnsResourceRecord>> entry in _entries)
types.Add(entry.Key);
if (!_entries.ContainsKey(DnsResourceRecordType.NSEC))
types.Add(DnsResourceRecordType.NSEC);
if (!_entries.ContainsKey(DnsResourceRecordType.RRSIG))
types.Add(DnsResourceRecordType.RRSIG);
types.Sort();
DnsNSECRecordData newNSecRecord = new DnsNSECRecordData(nextDomainName, types);
if (!_entries.TryGetValue(DnsResourceRecordType.NSEC, out IReadOnlyList<DnsResourceRecord> existingRecords) || (existingRecords[0].TtlValue != ttl) || !existingRecords[0].RDATA.Equals(newNSecRecord))
return new DnsResourceRecord[] { new DnsResourceRecord(_name, DnsResourceRecordType.NSEC, DnsClass.IN, ttl, newNSecRecord) };
return Array.Empty<DnsResourceRecord>();
}
internal IReadOnlyList<DnsResourceRecord> GetUpdatedNSec3RRSet(IReadOnlyList<DnsResourceRecord> newNSec3Records)
{
if (!_entries.TryGetValue(DnsResourceRecordType.NSEC3, out IReadOnlyList<DnsResourceRecord> existingRecords) || (existingRecords[0].TtlValue != newNSec3Records[0].TtlValue) || !existingRecords[0].RDATA.Equals(newNSec3Records[0].RDATA))
return newNSec3Records;
return Array.Empty<DnsResourceRecord>();
}
internal IReadOnlyList<DnsResourceRecord> CreateNSec3RRSet(string hashedOwnerName, byte[] nextHashedOwnerName, uint ttl, ushort iterations, byte[] salt)
{
List<DnsResourceRecordType> types = new List<DnsResourceRecordType>(_entries.Count);
foreach (KeyValuePair<DnsResourceRecordType, IReadOnlyList<DnsResourceRecord>> entry in _entries)
{
switch (entry.Key)
{
case DnsResourceRecordType.NSEC3:
case DnsResourceRecordType.RRSIG:
continue;
default:
types.Add(entry.Key);
break;
}
}
if (types.Count > 0)
{
//zone is not an empty non-terminal (ENT)
if (!_entries.ContainsKey(DnsResourceRecordType.RRSIG))
types.Add(DnsResourceRecordType.RRSIG);
}
types.Sort();
DnsNSEC3RecordData newNSec3 = new DnsNSEC3RecordData(DnssecNSEC3HashAlgorithm.SHA1, DnssecNSEC3Flags.None, iterations, salt, nextHashedOwnerName, types);
return new DnsResourceRecord[] { new DnsResourceRecord(hashedOwnerName, DnsResourceRecordType.NSEC3, DnsClass.IN, ttl, newNSec3) };
}
internal DnsResourceRecord GetPartialNSec3Record(string zoneName, uint ttl, ushort iterations, byte[] salt)
{
List<DnsResourceRecordType> types = new List<DnsResourceRecordType>(_entries.Count);
foreach (KeyValuePair<DnsResourceRecordType, IReadOnlyList<DnsResourceRecord>> entry in _entries)
{
switch (entry.Key)
{
case DnsResourceRecordType.NSEC3:
case DnsResourceRecordType.RRSIG:
continue;
default:
types.Add(entry.Key);
break;
}
}
if (_name.Equals(zoneName, StringComparison.OrdinalIgnoreCase))
{
types.Add(DnsResourceRecordType.NSEC3PARAM); //add NSEC3PARAM type to NSEC3 for unsigned zone apex
if (!_entries.ContainsKey(DnsResourceRecordType.RRSIG))
types.Add(DnsResourceRecordType.RRSIG);
}
else if (types.Count > 0)
{
//zone is not an empty non-terminal (ENT)
if (!_entries.ContainsKey(DnsResourceRecordType.RRSIG))
types.Add(DnsResourceRecordType.RRSIG);
}
types.Sort();
DnsNSEC3RecordData newNSec3Record = new DnsNSEC3RecordData(DnssecNSEC3HashAlgorithm.SHA1, DnssecNSEC3Flags.None, iterations, salt, Array.Empty<byte>(), types);
return new DnsResourceRecord(newNSec3Record.ComputeHashedOwnerName(_name) + (zoneName.Length > 0 ? "." + zoneName : ""), DnsResourceRecordType.NSEC3, DnsClass.IN, ttl, newNSec3Record);
}
#endregion
#region public
public void SyncRecords(Dictionary<DnsResourceRecordType, List<DnsResourceRecord>> newEntries)
{
//remove entires of type that do not exists in new entries
foreach (KeyValuePair<DnsResourceRecordType, IReadOnlyList<DnsResourceRecord>> entry in _entries)
{
if (!newEntries.ContainsKey(entry.Key))
_entries.TryRemove(entry.Key, out _);
}
//set new entries into zone
if (this is ForwarderZone)
{
//skip NS and SOA records from being added to ForwarderZone
foreach (KeyValuePair<DnsResourceRecordType, List<DnsResourceRecord>> newEntry in newEntries)
{
switch (newEntry.Key)
{
case DnsResourceRecordType.NS:
case DnsResourceRecordType.SOA:
break;
default:
_entries[newEntry.Key] = newEntry.Value;
break;
}
}
}
else
{
foreach (KeyValuePair<DnsResourceRecordType, List<DnsResourceRecord>> newEntry in newEntries)
{
if (newEntry.Key == DnsResourceRecordType.SOA)
{
if (newEntry.Value.Count != 1)
continue; //skip invalid SOA record
if (this is SecondaryZone)
{
//copy existing SOA record's info to new SOA record
DnsResourceRecord existingSoaRecord = _entries[DnsResourceRecordType.SOA][0];
DnsResourceRecord newSoaRecord = newEntry.Value[0];
newSoaRecord.CopyRecordInfoFrom(existingSoaRecord);
}
}
_entries[newEntry.Key] = newEntry.Value;
}
}
}
public void SyncRecords(Dictionary<DnsResourceRecordType, List<DnsResourceRecord>> deletedEntries, Dictionary<DnsResourceRecordType, List<DnsResourceRecord>> addedEntries)
{
if (deletedEntries is not null)
{
foreach (KeyValuePair<DnsResourceRecordType, List<DnsResourceRecord>> deletedEntry in deletedEntries)
{
if (_entries.TryGetValue(deletedEntry.Key, out IReadOnlyList<DnsResourceRecord> existingRecords))
{
List<DnsResourceRecord> updatedRecords = new List<DnsResourceRecord>(Math.Max(0, existingRecords.Count - deletedEntry.Value.Count));
foreach (DnsResourceRecord existingRecord in existingRecords)
{
bool deleted = false;
foreach (DnsResourceRecord deletedRecord in deletedEntry.Value)
{
if (existingRecord.RDATA.Equals(deletedRecord.RDATA))
{
deleted = true;
break;
}
}
if (!deleted)
updatedRecords.Add(existingRecord);
}
if (existingRecords.Count > updatedRecords.Count)
{
if (updatedRecords.Count > 0)
_entries[deletedEntry.Key] = updatedRecords;
else
_entries.TryRemove(deletedEntry.Key, out _);
}
}
}
}
if (addedEntries is not null)
{
foreach (KeyValuePair<DnsResourceRecordType, List<DnsResourceRecord>> addedEntry in addedEntries)
{
_entries.AddOrUpdate(addedEntry.Key, addedEntry.Value, delegate (DnsResourceRecordType key, IReadOnlyList<DnsResourceRecord> existingRecords)
{
List<DnsResourceRecord> updatedRecords = new List<DnsResourceRecord>(existingRecords.Count + addedEntry.Value.Count);
updatedRecords.AddRange(existingRecords);
foreach (DnsResourceRecord addedRecord in addedEntry.Value)
{
bool exists = false;
foreach (DnsResourceRecord existingRecord in existingRecords)
{
if (addedRecord.RDATA.Equals(existingRecord.RDATA))
{
exists = true;
break;
}
}
if (!exists)
updatedRecords.Add(addedRecord);
}
if (updatedRecords.Count > existingRecords.Count)
return updatedRecords;
else
return existingRecords;
});
}
}
}
public void SyncGlueRecords(IReadOnlyCollection<DnsResourceRecord> deletedGlueRecords, IReadOnlyCollection<DnsResourceRecord> addedGlueRecords)
{
if (_entries.TryGetValue(DnsResourceRecordType.NS, out IReadOnlyList<DnsResourceRecord> nsRecords))
{
foreach (DnsResourceRecord nsRecord in nsRecords)
nsRecord.SyncGlueRecords(deletedGlueRecords, addedGlueRecords);
}
}
public void LoadRecords(DnsResourceRecordType type, IReadOnlyList<DnsResourceRecord> records)
{
_entries[type] = records;
}
public virtual void SetRecords(DnsResourceRecordType type, IReadOnlyList<DnsResourceRecord> records)
{
_entries[type] = records;
}
public virtual void AddRecord(DnsResourceRecord record)
{
AddRecord(record, out _, out _);
}
public virtual bool DeleteRecords(DnsResourceRecordType type)
{
return _entries.TryRemove(type, out _);
}
public virtual bool DeleteRecord(DnsResourceRecordType type, DnsResourceRecordData rdata)
{
return TryDeleteRecord(type, rdata, out _);
}
public virtual void UpdateRecord(DnsResourceRecord oldRecord, DnsResourceRecord newRecord)
{
if (oldRecord.Type == DnsResourceRecordType.SOA)
throw new InvalidOperationException("Cannot update record: use SetRecords() for " + oldRecord.Type.ToString() + " record");
if (oldRecord.Type != newRecord.Type)
throw new InvalidOperationException("Old and new record types do not match.");
if (!DeleteRecord(oldRecord.Type, oldRecord.RDATA))
throw new DnsWebServiceException("Cannot update record: the old record does not exists.");
AddRecord(newRecord);
}
public virtual IReadOnlyList<DnsResourceRecord> QueryRecords(DnsResourceRecordType type, bool dnssecOk)
{
switch (type)
{
case DnsResourceRecordType.APP:
case DnsResourceRecordType.FWD:
case DnsResourceRecordType.NSEC:
case DnsResourceRecordType.NSEC3:
{
//return only exact type if exists
if (_entries.TryGetValue(type, out IReadOnlyList<DnsResourceRecord> existingRecords))
{
IReadOnlyList<DnsResourceRecord> filteredRecords = FilterDisabledRecords(type, existingRecords);
if (filteredRecords.Count > 0)
{
if (dnssecOk)
return AppendRRSigTo(filteredRecords);
return filteredRecords;
}
}
}
break;
case DnsResourceRecordType.ANY:
List<DnsResourceRecord> records = new List<DnsResourceRecord>(_entries.Count * 2);
foreach (KeyValuePair<DnsResourceRecordType, IReadOnlyList<DnsResourceRecord>> entry in _entries)
{
switch (entry.Key)
{
case DnsResourceRecordType.FWD:
case DnsResourceRecordType.APP:
//skip records
continue;
default:
records.AddRange(entry.Value);
break;
}
}
return FilterDisabledRecords(type, records);
default:
{
//check for CNAME
if (_entries.TryGetValue(DnsResourceRecordType.CNAME, out IReadOnlyList<DnsResourceRecord> existingCNAMERecords))
{
IReadOnlyList<DnsResourceRecord> filteredRecords = FilterDisabledRecords(type, existingCNAMERecords);
if (filteredRecords.Count > 0)
{
if (dnssecOk)
return AppendRRSigTo(filteredRecords);
return filteredRecords;
}
}
//check for exact type
if (_entries.TryGetValue(type, out IReadOnlyList<DnsResourceRecord> existingRecords))
{
IReadOnlyList<DnsResourceRecord> filteredRecords = FilterDisabledRecords(type, existingRecords);
if (filteredRecords.Count > 0)
{
if (dnssecOk)
return AppendRRSigTo(filteredRecords);
return filteredRecords;
}
}
//check special processing
switch (type)
{
case DnsResourceRecordType.A:
case DnsResourceRecordType.AAAA:
//check for ANAME
if (_entries.TryGetValue(DnsResourceRecordType.ANAME, out IReadOnlyList<DnsResourceRecord> anameRecords))
return FilterDisabledRecords(type, anameRecords);
break;
}
}
break;
}
return Array.Empty<DnsResourceRecord>();
}
public IReadOnlyList<DnsResourceRecord> GetRecords(DnsResourceRecordType type)
{
if (_entries.TryGetValue(type, out IReadOnlyList<DnsResourceRecord> records))
return records;
return Array.Empty<DnsResourceRecord>();
}
public override bool ContainsNameServerRecords()
{
if (!_entries.TryGetValue(DnsResourceRecordType.NS, out IReadOnlyList<DnsResourceRecord> records))
return false;
foreach (DnsResourceRecord record in records)
{
if (record.IsDisabled())
continue;
return true;
}
return false;
}
#endregion
#region properties
public virtual bool Disabled
{
get { return _disabled; }
set { _disabled = value; }
}
public virtual bool IsActive
{
get { return !_disabled; }
}
#endregion
}
}