mirror of
https://github.com/fergalmoran/DnsServer.git
synced 2026-01-07 01:04:02 +00:00
AuthZoneManager: implemented auth zone management changes. Added zone file management support.
This commit is contained in:
962
DnsServerCore/Dns/ZoneManagers/AuthZoneManager.cs
Normal file
962
DnsServerCore/Dns/ZoneManagers/AuthZoneManager.cs
Normal file
@@ -0,0 +1,962 @@
|
||||
/*
|
||||
Technitium DNS Server
|
||||
Copyright (C) 2020 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 DnsServerCore.Dns.Zones;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Net;
|
||||
using System.Text;
|
||||
using TechnitiumLibrary.Net.Dns;
|
||||
using TechnitiumLibrary.Net.Dns.ResourceRecords;
|
||||
|
||||
namespace DnsServerCore.Dns.ZoneManagers
|
||||
{
|
||||
public class AuthZoneManager
|
||||
{
|
||||
#region variables
|
||||
|
||||
readonly DnsServer _dnsServer;
|
||||
readonly string _serverDomain;
|
||||
|
||||
readonly ZoneTree<AuthZone> _root = new ZoneTree<AuthZone>();
|
||||
|
||||
#endregion
|
||||
|
||||
#region constructor
|
||||
|
||||
public AuthZoneManager(DnsServer dnsServer)
|
||||
{
|
||||
_dnsServer = dnsServer;
|
||||
if (_dnsServer == null)
|
||||
return; //allowed or blocked zone usage
|
||||
|
||||
_serverDomain = _dnsServer.ServerDomain;
|
||||
|
||||
LoadAllZoneFiles();
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region private
|
||||
|
||||
private void UpdateServerDomain(string serverDomain)
|
||||
{
|
||||
//update authoritative zone SOA and NS records
|
||||
List<AuthZoneInfo> zones = ListZones();
|
||||
|
||||
foreach (AuthZoneInfo zone in zones)
|
||||
{
|
||||
if (zone.Type != AuthZoneType.Primary)
|
||||
continue;
|
||||
|
||||
DnsResourceRecord record = zone.QueryRecords(DnsResourceRecordType.SOA)[0];
|
||||
DnsSOARecord soa = record.RDATA as DnsSOARecord;
|
||||
|
||||
if (soa.MasterNameServer.Equals(_serverDomain, StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
string responsiblePerson = soa.ResponsiblePerson;
|
||||
if (responsiblePerson.EndsWith(_serverDomain))
|
||||
responsiblePerson = responsiblePerson.Replace(_serverDomain, serverDomain);
|
||||
|
||||
SetRecords(record.Name, record.Type, record.TtlValue, new DnsResourceRecordData[] { new DnsSOARecord(serverDomain, responsiblePerson, soa.Serial, soa.Refresh, soa.Retry, soa.Expire, soa.Minimum) });
|
||||
|
||||
//update NS records
|
||||
IReadOnlyList<DnsResourceRecord> nsResourceRecords = zone.QueryRecords(DnsResourceRecordType.NS);
|
||||
|
||||
foreach (DnsResourceRecord nsResourceRecord in nsResourceRecords)
|
||||
{
|
||||
if ((nsResourceRecord.RDATA as DnsNSRecord).NSDomainName.Equals(_serverDomain, StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
UpdateRecord(nsResourceRecord, new DnsResourceRecord(nsResourceRecord.Name, nsResourceRecord.Type, nsResourceRecord.Class, nsResourceRecord.TtlValue, new DnsNSRecord(serverDomain)) { Tag = nsResourceRecord.Tag });
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (zone.Internal)
|
||||
continue; //dont save internal zones to disk
|
||||
|
||||
try
|
||||
{
|
||||
SaveZoneFile(zone.Name);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogManager log = _dnsServer.LogManager;
|
||||
if (log != null)
|
||||
log.Write(ex);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private AuthZone CreateEmptyZone(AuthZoneInfo zoneInfo)
|
||||
{
|
||||
AuthZone zone;
|
||||
|
||||
switch (zoneInfo.Type)
|
||||
{
|
||||
case AuthZoneType.Primary:
|
||||
zone = new PrimaryZone(_dnsServer, zoneInfo);
|
||||
break;
|
||||
|
||||
case AuthZoneType.Secondary:
|
||||
zone = new SecondaryZone(_dnsServer, zoneInfo);
|
||||
break;
|
||||
|
||||
case AuthZoneType.Stub:
|
||||
zone = new StubZone(_dnsServer, zoneInfo);
|
||||
break;
|
||||
|
||||
case AuthZoneType.Forwarder:
|
||||
zone = new ForwarderZone(zoneInfo);
|
||||
break;
|
||||
|
||||
default:
|
||||
throw new InvalidDataException("DNS zone type not supported.");
|
||||
}
|
||||
|
||||
if (_root.TryAdd(zone))
|
||||
return zone;
|
||||
|
||||
throw new DnsServerException("Zone already exists: " + zoneInfo.Name);
|
||||
}
|
||||
|
||||
private void LoadRecords(IReadOnlyList<DnsResourceRecord> records)
|
||||
{
|
||||
Dictionary<string, Dictionary<DnsResourceRecordType, List<DnsResourceRecord>>> groupedByDomainRecords = DnsResourceRecord.GroupRecords(records);
|
||||
|
||||
foreach (KeyValuePair<string, Dictionary<DnsResourceRecordType, List<DnsResourceRecord>>> groupedByTypeRecords in groupedByDomainRecords)
|
||||
{
|
||||
AuthZone zone = GetOrAddZone(groupedByTypeRecords.Key);
|
||||
|
||||
foreach (KeyValuePair<DnsResourceRecordType, List<DnsResourceRecord>> groupedRecords in groupedByTypeRecords.Value)
|
||||
zone.LoadRecords(groupedRecords.Key, groupedRecords.Value);
|
||||
|
||||
if (zone is SubDomainZone)
|
||||
(zone as SubDomainZone).AutoUpdateState();
|
||||
}
|
||||
}
|
||||
|
||||
private AuthZone GetAuthoritativeZone(string domain)
|
||||
{
|
||||
_ = _root.FindZone(domain, out _, out AuthZone authority, out _);
|
||||
if (authority == null)
|
||||
return null;
|
||||
|
||||
return authority;
|
||||
}
|
||||
|
||||
private AuthZone GetOrAddZone(string domain)
|
||||
{
|
||||
return _root.GetOrAdd(domain, delegate (string key)
|
||||
{
|
||||
AuthZone authZone = GetAuthoritativeZone(domain);
|
||||
if (authZone == null)
|
||||
throw new DnsServerException("Zone not found.");
|
||||
|
||||
if (authZone is PrimaryZone)
|
||||
return new PrimarySubDomainZone(authZone as PrimaryZone, domain);
|
||||
else if (authZone is SecondaryZone)
|
||||
return new SecondarySubDomainZone(domain);
|
||||
else if (authZone is StubZone)
|
||||
return new StubSubDomainZone(domain);
|
||||
else if (authZone is ForwarderZone)
|
||||
return new ForwarderSubDomainZone(domain);
|
||||
|
||||
throw new DnsServerException("Zone cannot have sub domains.");
|
||||
});
|
||||
}
|
||||
|
||||
private IReadOnlyList<DnsResourceRecord> GetAdditionalRecords(IReadOnlyList<DnsResourceRecord> nsRecords)
|
||||
{
|
||||
IReadOnlyList<DnsResourceRecord> glueRecords = nsRecords.GetGlueRecords();
|
||||
if (glueRecords.Count > 0)
|
||||
return glueRecords;
|
||||
|
||||
List<DnsResourceRecord> additionalRecords = new List<DnsResourceRecord>();
|
||||
|
||||
foreach (DnsResourceRecord nsRecord in nsRecords)
|
||||
{
|
||||
if (nsRecord.Type != DnsResourceRecordType.NS)
|
||||
continue;
|
||||
|
||||
AuthZone authZone = _root.FindZone((nsRecord.RDATA as DnsNSRecord).NSDomainName, out _, out _, out _);
|
||||
if ((authZone != null) && !authZone.Disabled)
|
||||
{
|
||||
{
|
||||
IReadOnlyList<DnsResourceRecord> records = authZone.QueryRecords(DnsResourceRecordType.A);
|
||||
if ((records.Count > 0) && (records[0].RDATA is DnsARecord))
|
||||
additionalRecords.AddRange(records);
|
||||
}
|
||||
|
||||
{
|
||||
IReadOnlyList<DnsResourceRecord> records = authZone.QueryRecords(DnsResourceRecordType.AAAA);
|
||||
if ((records.Count > 0) && (records[0].RDATA is DnsAAAARecord))
|
||||
additionalRecords.AddRange(records);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return additionalRecords;
|
||||
}
|
||||
|
||||
private void LoadAllZoneFiles()
|
||||
{
|
||||
string zonesFolder = Path.Combine(_dnsServer.ConfigFolder, "zones");
|
||||
if (!Directory.Exists(zonesFolder))
|
||||
Directory.CreateDirectory(zonesFolder);
|
||||
|
||||
//move zone files to new folder
|
||||
{
|
||||
string[] oldZoneFiles = Directory.GetFiles(_dnsServer.ConfigFolder, "*.zone");
|
||||
|
||||
foreach (string oldZoneFile in oldZoneFiles)
|
||||
File.Move(oldZoneFile, Path.Combine(zonesFolder, Path.GetFileName(oldZoneFile)));
|
||||
}
|
||||
|
||||
//remove old internal zones
|
||||
{
|
||||
string[] oldZoneFiles = new string[] { "localhost.zone", "1.0.0.127.in-addr.arpa.zone", "1.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.ip6.arpa.zone" };
|
||||
|
||||
foreach (string oldZoneFile in oldZoneFiles)
|
||||
{
|
||||
string filePath = Path.Combine(zonesFolder, oldZoneFile);
|
||||
|
||||
if (File.Exists(filePath))
|
||||
{
|
||||
try
|
||||
{
|
||||
File.Delete(filePath);
|
||||
}
|
||||
catch
|
||||
{ }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//load system zones
|
||||
{
|
||||
{
|
||||
CreatePrimaryZone("localhost", _dnsServer.ServerDomain, true);
|
||||
SetRecords("localhost", DnsResourceRecordType.A, 3600, new DnsResourceRecordData[] { new DnsARecord(IPAddress.Loopback) });
|
||||
SetRecords("localhost", DnsResourceRecordType.AAAA, 3600, new DnsResourceRecordData[] { new DnsAAAARecord(IPAddress.IPv6Loopback) });
|
||||
}
|
||||
|
||||
{
|
||||
string prtDomain = "0.in-addr.arpa";
|
||||
|
||||
CreatePrimaryZone(prtDomain, _dnsServer.ServerDomain, true);
|
||||
}
|
||||
|
||||
{
|
||||
string prtDomain = "255.in-addr.arpa";
|
||||
|
||||
CreatePrimaryZone(prtDomain, _dnsServer.ServerDomain, true);
|
||||
}
|
||||
|
||||
{
|
||||
string prtDomain = "127.in-addr.arpa";
|
||||
|
||||
CreatePrimaryZone(prtDomain, _dnsServer.ServerDomain, true);
|
||||
SetRecords("1.0.0.127.in-addr.arpa", DnsResourceRecordType.PTR, 3600, new DnsResourceRecordData[] { new DnsPTRRecord("localhost") });
|
||||
}
|
||||
|
||||
{
|
||||
string prtDomain = new DnsQuestionRecord(IPAddress.IPv6Loopback, DnsClass.IN).Name;
|
||||
|
||||
CreatePrimaryZone(prtDomain, _dnsServer.ServerDomain, true);
|
||||
SetRecords(prtDomain, DnsResourceRecordType.PTR, 3600, new DnsResourceRecordData[] { new DnsPTRRecord("localhost") });
|
||||
}
|
||||
}
|
||||
|
||||
//load zone files
|
||||
string[] zoneFiles = Directory.GetFiles(zonesFolder, "*.zone");
|
||||
|
||||
foreach (string zoneFile in zoneFiles)
|
||||
{
|
||||
try
|
||||
{
|
||||
using (FileStream fS = new FileStream(zoneFile, FileMode.Open, FileAccess.Read))
|
||||
{
|
||||
LoadZoneFrom(fS);
|
||||
}
|
||||
|
||||
LogManager log = _dnsServer.LogManager;
|
||||
if (log != null)
|
||||
log.Write("DNS Server successfully loaded zone file: " + zoneFile);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogManager log = _dnsServer.LogManager;
|
||||
if (log != null)
|
||||
log.Write("DNS Server failed to load zone file: " + zoneFile + "\r\n" + ex.ToString());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private DnsDatagram GetReferralResponse(DnsDatagram request, AuthZone delegationZone)
|
||||
{
|
||||
IReadOnlyList<DnsResourceRecord> authority = delegationZone.QueryRecords(DnsResourceRecordType.NS);
|
||||
IReadOnlyList<DnsResourceRecord> additional = GetAdditionalRecords(authority);
|
||||
|
||||
return new DnsDatagram(request.Identifier, true, DnsOpcode.StandardQuery, false, false, request.RecursionDesired, false, false, false, DnsResponseCode.NoError, request.Question, null, authority, additional);
|
||||
}
|
||||
|
||||
private DnsDatagram GetForwarderResponse(DnsDatagram request, AuthZone forwarderZone)
|
||||
{
|
||||
IReadOnlyList<DnsResourceRecord> authority = forwarderZone.QueryRecords(DnsResourceRecordType.FWD);
|
||||
|
||||
return new DnsDatagram(request.Identifier, true, DnsOpcode.StandardQuery, false, false, request.RecursionDesired, false, false, false, DnsResponseCode.NoError, request.Question, null, authority);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region public
|
||||
|
||||
public AuthZoneInfo CreatePrimaryZone(string domain, string masterNameServer, bool @internal)
|
||||
{
|
||||
AuthZone authZone = new PrimaryZone(_dnsServer, domain, new DnsSOARecord(masterNameServer, "hostmaster." + masterNameServer, 1, 14400, 3600, 604800, 900), @internal);
|
||||
|
||||
if (_root.TryAdd(authZone))
|
||||
return new AuthZoneInfo(authZone);
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public AuthZoneInfo CreatePrimaryZone(string domain, DnsSOARecord soaRecord, DnsNSRecord ns, bool @internal)
|
||||
{
|
||||
AuthZone authZone = new PrimaryZone(_dnsServer, domain, soaRecord, ns, @internal);
|
||||
|
||||
if (_root.TryAdd(authZone))
|
||||
return new AuthZoneInfo(authZone);
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public AuthZoneInfo CreateSecondaryZone(string domain, string masterNameServer)
|
||||
{
|
||||
AuthZone authZone = new SecondaryZone(_dnsServer, domain, new DnsSOARecord(masterNameServer, "hostmaster." + masterNameServer, 1, 14400, 3600, 604800, 900));
|
||||
|
||||
if (_root.TryAdd(authZone))
|
||||
{
|
||||
(authZone as SecondaryZone).RefreshZone();
|
||||
return new AuthZoneInfo(authZone);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public AuthZoneInfo CreateStubZone(string domain, string masterNameServer)
|
||||
{
|
||||
AuthZone authZone = new StubZone(_dnsServer, domain, new DnsSOARecord(masterNameServer, "hostmaster." + masterNameServer, 1, 14400, 3600, 604800, 900));
|
||||
|
||||
if (_root.TryAdd(authZone))
|
||||
{
|
||||
(authZone as StubZone).RefreshZone();
|
||||
return new AuthZoneInfo(authZone);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public AuthZoneInfo CreateForwarderZone(string domain, DnsTransportProtocol forwarderProtocol, string forwarder)
|
||||
{
|
||||
AuthZone authZone = new ForwarderZone(domain);
|
||||
|
||||
DnsResourceRecord record = new DnsResourceRecord(domain, DnsResourceRecordType.FWD, DnsClass.IN, 0, new DnsForwarderRecord(forwarderProtocol, forwarder));
|
||||
authZone.LoadRecords(DnsResourceRecordType.FWD, new DnsResourceRecord[] { record });
|
||||
|
||||
if (_root.TryAdd(authZone))
|
||||
return new AuthZoneInfo(authZone);
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public bool DeleteZone(string domain)
|
||||
{
|
||||
if (_root.TryRemove(domain, out AuthZone authZone))
|
||||
{
|
||||
authZone.Dispose();
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public AuthZoneInfo GetZoneInfo(string domain)
|
||||
{
|
||||
_ = _root.FindZone(domain, out _, out AuthZone authority, out _);
|
||||
if (authority == null)
|
||||
return null;
|
||||
|
||||
return new AuthZoneInfo(authority);
|
||||
}
|
||||
|
||||
public List<DnsResourceRecord> ListAllRecords(string domain)
|
||||
{
|
||||
List<DnsResourceRecord> records = new List<DnsResourceRecord>();
|
||||
|
||||
foreach (AuthZone zone in _root.GetZoneWithSubDomainZones(domain))
|
||||
records.AddRange(zone.ListAllRecords());
|
||||
|
||||
return records;
|
||||
}
|
||||
|
||||
public IReadOnlyList<DnsResourceRecord> QueryRecords(string domain, DnsResourceRecordType type)
|
||||
{
|
||||
if (_root.TryGet(domain, out AuthZone zone))
|
||||
return zone.QueryRecords(type);
|
||||
|
||||
return Array.Empty<DnsResourceRecord>();
|
||||
}
|
||||
|
||||
public IReadOnlyList<DnsResourceRecord> QueryZoneTransferRecords(string domain)
|
||||
{
|
||||
List<DnsResourceRecord> axfrRecords = new List<DnsResourceRecord>();
|
||||
|
||||
List<AuthZone> zones = _root.GetZoneWithSubDomainZones(domain);
|
||||
|
||||
if ((zones.Count > 0) && (zones[0] is PrimaryZone) && !zones[0].Disabled)
|
||||
{
|
||||
//only primary zones support zone transfer
|
||||
DnsResourceRecord soaRecord = zones[0].QueryRecords(DnsResourceRecordType.SOA)[0];
|
||||
|
||||
axfrRecords.Add(soaRecord);
|
||||
|
||||
foreach (Zone zone in zones)
|
||||
{
|
||||
foreach (DnsResourceRecord record in zone.ListAllRecords())
|
||||
{
|
||||
if (record.IsDisabled())
|
||||
continue;
|
||||
|
||||
switch (record.Type)
|
||||
{
|
||||
case DnsResourceRecordType.SOA:
|
||||
//skip
|
||||
break;
|
||||
|
||||
case DnsResourceRecordType.NS:
|
||||
axfrRecords.Add(record);
|
||||
axfrRecords.AddRange(record.GetGlueRecords());
|
||||
break;
|
||||
|
||||
default:
|
||||
axfrRecords.Add(record);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
axfrRecords.Add(soaRecord);
|
||||
}
|
||||
|
||||
return axfrRecords;
|
||||
}
|
||||
|
||||
public void SyncRecords(string domain, IReadOnlyList<DnsResourceRecord> syncRecords, IReadOnlyList<DnsResourceRecord> additionalRecords = null, bool dontRemove = false)
|
||||
{
|
||||
List<DnsResourceRecord> newRecords = new List<DnsResourceRecord>(syncRecords.Count);
|
||||
List<DnsResourceRecord> glueRecords = new List<DnsResourceRecord>();
|
||||
|
||||
if (additionalRecords != null)
|
||||
glueRecords.AddRange(additionalRecords);
|
||||
|
||||
int i = 0;
|
||||
|
||||
if (syncRecords[0].Type == DnsResourceRecordType.SOA)
|
||||
i = 1; //skip first SOA in AXFR
|
||||
|
||||
for (; i < syncRecords.Count; i++)
|
||||
{
|
||||
DnsResourceRecord record = syncRecords[i];
|
||||
|
||||
if (record.Name.EndsWith(domain, StringComparison.OrdinalIgnoreCase))
|
||||
newRecords.Add(record);
|
||||
else
|
||||
glueRecords.Add(record);
|
||||
}
|
||||
|
||||
if (glueRecords.Count > 0)
|
||||
{
|
||||
foreach (DnsResourceRecord record in newRecords)
|
||||
{
|
||||
if (record.Type == DnsResourceRecordType.NS)
|
||||
record.SetGlueRecords(glueRecords);
|
||||
}
|
||||
}
|
||||
|
||||
List<DnsResourceRecord> oldRecords = ListAllRecords(domain);
|
||||
|
||||
Dictionary<string, Dictionary<DnsResourceRecordType, List<DnsResourceRecord>>> newRecordsGroupedByDomain = DnsResourceRecord.GroupRecords(newRecords);
|
||||
Dictionary<string, Dictionary<DnsResourceRecordType, List<DnsResourceRecord>>> oldRecordsGroupedByDomain = DnsResourceRecord.GroupRecords(oldRecords);
|
||||
|
||||
if (!dontRemove)
|
||||
{
|
||||
//remove domains that do not exists in new records
|
||||
foreach (string oldDomain in oldRecordsGroupedByDomain.Keys)
|
||||
{
|
||||
if (!newRecordsGroupedByDomain.ContainsKey(oldDomain))
|
||||
_root.TryRemove(oldDomain, out _);
|
||||
}
|
||||
}
|
||||
|
||||
//sync new records
|
||||
foreach (KeyValuePair<string, Dictionary<DnsResourceRecordType, List<DnsResourceRecord>>> newEntries in newRecordsGroupedByDomain)
|
||||
{
|
||||
AuthZone zone = GetOrAddZone(newEntries.Key);
|
||||
zone.SyncRecords(newEntries.Value, dontRemove);
|
||||
}
|
||||
}
|
||||
|
||||
public void SetRecords(string domain, DnsResourceRecordType type, uint ttl, DnsResourceRecordData[] records)
|
||||
{
|
||||
DnsResourceRecord[] resourceRecords = new DnsResourceRecord[records.Length];
|
||||
|
||||
for (int i = 0; i < records.Length; i++)
|
||||
resourceRecords[i] = new DnsResourceRecord(domain, type, DnsClass.IN, ttl, records[i]);
|
||||
|
||||
AuthZone zone = GetOrAddZone(domain);
|
||||
|
||||
zone.SetRecords(type, resourceRecords);
|
||||
|
||||
if (zone is SubDomainZone)
|
||||
(zone as SubDomainZone).AutoUpdateState();
|
||||
}
|
||||
|
||||
public void AddRecord(string domain, DnsResourceRecordType type, uint ttl, DnsResourceRecordData record)
|
||||
{
|
||||
AuthZone zone = GetOrAddZone(domain);
|
||||
|
||||
zone.AddRecord(new DnsResourceRecord(zone.Name, type, DnsClass.IN, ttl, record));
|
||||
|
||||
if (zone is SubDomainZone)
|
||||
(zone as SubDomainZone).AutoUpdateState();
|
||||
}
|
||||
|
||||
public void UpdateRecord(DnsResourceRecord oldRecord, DnsResourceRecord newRecord)
|
||||
{
|
||||
if (oldRecord.Type != newRecord.Type)
|
||||
throw new DnsServerException("Cannot update record: new record must be of same type.");
|
||||
|
||||
if (oldRecord.Type == DnsResourceRecordType.SOA)
|
||||
throw new DnsServerException("Cannot update record: use SetRecords() for updating SOA record.");
|
||||
|
||||
if (!_root.TryGet(oldRecord.Name, out AuthZone zone))
|
||||
throw new DnsServerException("Cannot update record: zone does not exists.");
|
||||
|
||||
switch (oldRecord.Type)
|
||||
{
|
||||
case DnsResourceRecordType.CNAME:
|
||||
case DnsResourceRecordType.PTR:
|
||||
if (oldRecord.Name.Equals(newRecord.Name, StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
zone.SetRecords(newRecord.Type, new DnsResourceRecord[] { newRecord });
|
||||
|
||||
if (zone is SubDomainZone)
|
||||
(zone as SubDomainZone).AutoUpdateState();
|
||||
}
|
||||
else
|
||||
{
|
||||
zone.DeleteRecords(oldRecord.Type);
|
||||
|
||||
if (zone is SubDomainZone)
|
||||
{
|
||||
if (zone.IsEmpty)
|
||||
_root.TryRemove(oldRecord.Name, out _); //remove empty sub zone
|
||||
else
|
||||
(zone as SubDomainZone).AutoUpdateState();
|
||||
}
|
||||
|
||||
AuthZone newZone = GetOrAddZone(newRecord.Name);
|
||||
|
||||
newZone.SetRecords(newRecord.Type, new DnsResourceRecord[] { newRecord });
|
||||
|
||||
if (newZone is SubDomainZone)
|
||||
(newZone as SubDomainZone).AutoUpdateState();
|
||||
}
|
||||
break;
|
||||
|
||||
default:
|
||||
if (oldRecord.Name.Equals(newRecord.Name, StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
zone.DeleteRecord(oldRecord.Type, oldRecord.RDATA);
|
||||
zone.AddRecord(newRecord);
|
||||
|
||||
if (zone is SubDomainZone)
|
||||
(zone as SubDomainZone).AutoUpdateState();
|
||||
}
|
||||
else
|
||||
{
|
||||
zone.DeleteRecord(oldRecord.Type, oldRecord.RDATA);
|
||||
|
||||
if (zone is SubDomainZone)
|
||||
{
|
||||
if (zone.IsEmpty)
|
||||
_root.TryRemove(oldRecord.Name, out _); //remove empty sub zone
|
||||
else
|
||||
(zone as SubDomainZone).AutoUpdateState();
|
||||
}
|
||||
|
||||
AuthZone newZone = GetOrAddZone(newRecord.Name);
|
||||
|
||||
newZone.AddRecord(newRecord);
|
||||
|
||||
if (newZone is SubDomainZone)
|
||||
(newZone as SubDomainZone).AutoUpdateState();
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
public void DeleteRecord(string domain, DnsResourceRecordType type, DnsResourceRecordData record)
|
||||
{
|
||||
if (_root.TryGet(domain, out AuthZone zone))
|
||||
{
|
||||
zone.DeleteRecord(type, record);
|
||||
|
||||
if (zone is SubDomainZone)
|
||||
{
|
||||
if (zone.IsEmpty)
|
||||
_root.TryRemove(domain, out _); //remove empty sub zone
|
||||
else
|
||||
(zone as SubDomainZone).AutoUpdateState();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void DeleteRecords(string domain, DnsResourceRecordType type)
|
||||
{
|
||||
if (_root.TryGet(domain, out AuthZone zone))
|
||||
{
|
||||
zone.DeleteRecords(type);
|
||||
|
||||
if (zone is SubDomainZone)
|
||||
{
|
||||
if (zone.IsEmpty)
|
||||
_root.TryRemove(domain, out _); //remove empty sub zone
|
||||
else
|
||||
(zone as SubDomainZone).AutoUpdateState();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public List<AuthZoneInfo> ListZones()
|
||||
{
|
||||
List<AuthZoneInfo> zones = new List<AuthZoneInfo>();
|
||||
|
||||
foreach (AuthZone zone in _root)
|
||||
{
|
||||
AuthZoneInfo zoneInfo = new AuthZoneInfo(zone);
|
||||
switch (zoneInfo.Type)
|
||||
{
|
||||
case AuthZoneType.Primary:
|
||||
case AuthZoneType.Secondary:
|
||||
case AuthZoneType.Stub:
|
||||
case AuthZoneType.Forwarder:
|
||||
zones.Add(zoneInfo);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return zones;
|
||||
}
|
||||
|
||||
public List<string> ListSubDomains(string domain)
|
||||
{
|
||||
return _root.ListSubDomains(domain);
|
||||
}
|
||||
|
||||
public DnsDatagram Query(DnsDatagram request)
|
||||
{
|
||||
AuthZone zone = _root.FindZone(request.Question[0].Name, out AuthZone delegation, out AuthZone authZone, out bool hasSubDomains);
|
||||
|
||||
if ((authZone == null) || authZone.Disabled) //no authority for requested zone
|
||||
return new DnsDatagram(request.Identifier, true, DnsOpcode.StandardQuery, false, false, request.RecursionDesired, false, false, false, DnsResponseCode.Refused, request.Question);
|
||||
|
||||
if ((delegation != null) && !delegation.Disabled)
|
||||
return GetReferralResponse(request, delegation);
|
||||
|
||||
if ((zone == null) || zone.Disabled)
|
||||
{
|
||||
//zone not found
|
||||
if (authZone is StubZone)
|
||||
return GetReferralResponse(request, authZone);
|
||||
else if (authZone is ForwarderZone)
|
||||
return GetForwarderResponse(request, authZone);
|
||||
|
||||
DnsResponseCode rCode = hasSubDomains ? DnsResponseCode.NoError : DnsResponseCode.NameError;
|
||||
IReadOnlyList<DnsResourceRecord> authority = authZone.QueryRecords(DnsResourceRecordType.SOA);
|
||||
|
||||
return new DnsDatagram(request.Identifier, true, DnsOpcode.StandardQuery, true, false, request.RecursionDesired, false, false, false, rCode, request.Question, null, authority);
|
||||
}
|
||||
else
|
||||
{
|
||||
//zone found
|
||||
IReadOnlyList<DnsResourceRecord> authority;
|
||||
IReadOnlyList<DnsResourceRecord> additional;
|
||||
|
||||
IReadOnlyList<DnsResourceRecord> answers = zone.QueryRecords(request.Question[0].Type);
|
||||
if (answers.Count == 0)
|
||||
{
|
||||
//record type not found
|
||||
if (authZone is StubZone)
|
||||
return GetReferralResponse(request, authZone);
|
||||
else if (authZone is ForwarderZone)
|
||||
return GetForwarderResponse(request, authZone);
|
||||
|
||||
authority = authZone.QueryRecords(DnsResourceRecordType.SOA);
|
||||
additional = null;
|
||||
}
|
||||
else
|
||||
{
|
||||
//record type found
|
||||
if (zone.Name.Contains("*"))
|
||||
{
|
||||
//wildcard zone; generate new answer records
|
||||
DnsResourceRecord[] wildcardAnswers = new DnsResourceRecord[answers.Count];
|
||||
|
||||
for (int i = 0; i < answers.Count; i++)
|
||||
wildcardAnswers[i] = new DnsResourceRecord(request.Question[0].Name, answers[i].Type, answers[i].Class, answers[i].TtlValue, answers[i].RDATA) { Tag = answers[i].Tag };
|
||||
|
||||
answers = wildcardAnswers;
|
||||
}
|
||||
|
||||
switch (request.Question[0].Type)
|
||||
{
|
||||
case DnsResourceRecordType.NS:
|
||||
authority = null;
|
||||
additional = GetAdditionalRecords(answers);
|
||||
break;
|
||||
|
||||
case DnsResourceRecordType.ANY:
|
||||
authority = null;
|
||||
additional = null;
|
||||
break;
|
||||
|
||||
default:
|
||||
authority = authZone.QueryRecords(DnsResourceRecordType.NS);
|
||||
additional = GetAdditionalRecords(authority);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return new DnsDatagram(request.Identifier, true, DnsOpcode.StandardQuery, true, false, request.RecursionDesired, false, false, false, DnsResponseCode.NoError, request.Question, answers, authority, additional);
|
||||
}
|
||||
}
|
||||
|
||||
public void LoadZoneFrom(Stream s)
|
||||
{
|
||||
BinaryReader bR = new BinaryReader(s);
|
||||
|
||||
if (Encoding.ASCII.GetString(bR.ReadBytes(2)) != "DZ")
|
||||
throw new InvalidDataException("DnsServer zone file format is invalid.");
|
||||
|
||||
switch (bR.ReadByte())
|
||||
{
|
||||
case 2:
|
||||
{
|
||||
DnsResourceRecord[] records = new DnsResourceRecord[bR.ReadInt32()];
|
||||
if (records.Length > 0)
|
||||
{
|
||||
for (int i = 0; i < records.Length; i++)
|
||||
records[i] = new DnsResourceRecord(s);
|
||||
|
||||
//make zone info
|
||||
AuthZoneInfo zoneInfo = new AuthZoneInfo(records[0].Name, AuthZoneType.Primary, false);
|
||||
|
||||
//create zone
|
||||
Zone authZone = CreateEmptyZone(zoneInfo);
|
||||
|
||||
//load records
|
||||
LoadRecords(records);
|
||||
|
||||
//init zone
|
||||
switch (zoneInfo.Type)
|
||||
{
|
||||
case AuthZoneType.Primary:
|
||||
(authZone as PrimaryZone).NotifyNameServers();
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
case 3:
|
||||
{
|
||||
bool zoneDisabled = bR.ReadBoolean();
|
||||
DnsResourceRecord[] records = new DnsResourceRecord[bR.ReadInt32()];
|
||||
if (records.Length > 0)
|
||||
{
|
||||
for (int i = 0; i < records.Length; i++)
|
||||
{
|
||||
records[i] = new DnsResourceRecord(s);
|
||||
records[i].Tag = new DnsResourceRecordInfo(bR);
|
||||
}
|
||||
|
||||
//make zone info
|
||||
AuthZoneInfo zoneInfo = new AuthZoneInfo(records[0].Name, AuthZoneType.Primary, zoneDisabled);
|
||||
|
||||
//create zone
|
||||
Zone authZone = CreateEmptyZone(zoneInfo);
|
||||
|
||||
//load records
|
||||
LoadRecords(records);
|
||||
|
||||
//init zone
|
||||
switch (zoneInfo.Type)
|
||||
{
|
||||
case AuthZoneType.Primary:
|
||||
(authZone as PrimaryZone).NotifyNameServers();
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
case 4:
|
||||
{
|
||||
//read zone info
|
||||
AuthZoneInfo zoneInfo = new AuthZoneInfo(bR);
|
||||
|
||||
//create zone
|
||||
Zone authZone = CreateEmptyZone(zoneInfo);
|
||||
|
||||
//read all zone records
|
||||
DnsResourceRecord[] records = new DnsResourceRecord[bR.ReadInt32()];
|
||||
if (records.Length > 0)
|
||||
{
|
||||
for (int i = 0; i < records.Length; i++)
|
||||
{
|
||||
records[i] = new DnsResourceRecord(s);
|
||||
records[i].Tag = new DnsResourceRecordInfo(bR);
|
||||
}
|
||||
|
||||
//load records
|
||||
LoadRecords(records);
|
||||
|
||||
//init zone
|
||||
switch (zoneInfo.Type)
|
||||
{
|
||||
case AuthZoneType.Primary:
|
||||
(authZone as PrimaryZone).NotifyNameServers();
|
||||
break;
|
||||
|
||||
case AuthZoneType.Secondary:
|
||||
(authZone as SecondaryZone).RefreshZone();
|
||||
break;
|
||||
|
||||
case AuthZoneType.Stub:
|
||||
(authZone as StubZone).RefreshZone();
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
default:
|
||||
throw new InvalidDataException("DNS Zone file version not supported.");
|
||||
}
|
||||
}
|
||||
|
||||
public void WriteZoneTo(string domain, Stream s)
|
||||
{
|
||||
List<AuthZone> zones = _root.GetZoneWithSubDomainZones(domain);
|
||||
if (zones.Count == 0)
|
||||
throw new DnsServerException("Zone was not found: " + domain);
|
||||
|
||||
//serialize zone
|
||||
BinaryWriter bW = new BinaryWriter(s);
|
||||
|
||||
bW.Write(Encoding.ASCII.GetBytes("DZ")); //format
|
||||
bW.Write((byte)4); //version
|
||||
|
||||
//write zone info
|
||||
AuthZoneInfo zoneInfo = new AuthZoneInfo(zones[0]);
|
||||
zoneInfo.WriteTo(bW);
|
||||
|
||||
//write all zone records
|
||||
List<DnsResourceRecord> records = new List<DnsResourceRecord>();
|
||||
|
||||
foreach (AuthZone zone in zones)
|
||||
records.AddRange(zone.ListAllRecords());
|
||||
|
||||
bW.Write(records.Count);
|
||||
|
||||
foreach (DnsResourceRecord record in records)
|
||||
{
|
||||
record.WriteTo(s);
|
||||
|
||||
DnsResourceRecordInfo rrInfo = record.Tag as DnsResourceRecordInfo;
|
||||
if (rrInfo == null)
|
||||
rrInfo = new DnsResourceRecordInfo(); //default info
|
||||
|
||||
rrInfo.WriteTo(bW);
|
||||
}
|
||||
}
|
||||
|
||||
public void SaveZoneFile(string domain)
|
||||
{
|
||||
domain = domain.ToLower();
|
||||
|
||||
using (MemoryStream mS = new MemoryStream())
|
||||
{
|
||||
//serialize zone
|
||||
WriteZoneTo(domain, mS);
|
||||
|
||||
//write to zone file
|
||||
mS.Position = 0;
|
||||
|
||||
using (FileStream fS = new FileStream(Path.Combine(_dnsServer.ConfigFolder, "zones", domain + ".zone"), FileMode.Create, FileAccess.Write))
|
||||
{
|
||||
mS.CopyTo(fS);
|
||||
}
|
||||
}
|
||||
|
||||
LogManager log = _dnsServer.LogManager;
|
||||
if (log != null)
|
||||
log.Write("Saved zone file for domain: " + domain);
|
||||
}
|
||||
|
||||
public void DeleteZoneFile(string domain)
|
||||
{
|
||||
domain = domain.ToLower();
|
||||
|
||||
File.Delete(Path.Combine(_dnsServer.ConfigFolder, "zones", domain + ".zone"));
|
||||
|
||||
LogManager log = _dnsServer.LogManager;
|
||||
if (log != null)
|
||||
log.Write("Deleted zone file for domain: " + domain);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region properties
|
||||
|
||||
public string ServerDomain
|
||||
{
|
||||
get { return _serverDomain; }
|
||||
set { UpdateServerDomain(value); }
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
@@ -1,602 +0,0 @@
|
||||
/*
|
||||
Technitium DNS Server
|
||||
Copyright (C) 2020 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 System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Net;
|
||||
using System.Net.Sockets;
|
||||
using System.Text;
|
||||
using TechnitiumLibrary.Net.Dns;
|
||||
using TechnitiumLibrary.Net.Dns.ResourceRecords;
|
||||
|
||||
namespace DnsServerCore.Dns.Zones
|
||||
{
|
||||
public class AuthZoneManager
|
||||
{
|
||||
#region variables
|
||||
|
||||
readonly ZoneTree<AuthZone> _root = new ZoneTree<AuthZone>();
|
||||
|
||||
#endregion
|
||||
|
||||
#region private
|
||||
|
||||
private void CreateZone(AuthZoneInfo zoneInfo)
|
||||
{
|
||||
//create zone
|
||||
switch (zoneInfo.Type)
|
||||
{
|
||||
case AuthZoneType.Primary:
|
||||
if (!_root.TryAdd(new PrimaryZone(zoneInfo.Name, zoneInfo.Disabled)))
|
||||
throw new DnsServerException("Zone already exists: " + zoneInfo.Name);
|
||||
|
||||
break;
|
||||
|
||||
case AuthZoneType.Secondary:
|
||||
if (!_root.TryAdd(new SecondaryZone(zoneInfo.Name, zoneInfo.Disabled)))
|
||||
throw new DnsServerException("Zone already exists: " + zoneInfo.Name);
|
||||
|
||||
break;
|
||||
|
||||
case AuthZoneType.Stub:
|
||||
if (!_root.TryAdd(new StubZone(zoneInfo.Name, zoneInfo.Disabled)))
|
||||
throw new DnsServerException("Zone already exists: " + zoneInfo.Name);
|
||||
|
||||
break;
|
||||
|
||||
default:
|
||||
throw new InvalidDataException("DNS Zone type not supported.");
|
||||
}
|
||||
}
|
||||
|
||||
private IReadOnlyList<DnsResourceRecord> GetAdditionalRecords(IReadOnlyList<DnsResourceRecord> nsRecords)
|
||||
{
|
||||
IReadOnlyList<DnsResourceRecord> glueRecords = nsRecords.GetGlueRecords();
|
||||
if (glueRecords.Count > 0)
|
||||
return glueRecords;
|
||||
|
||||
List<DnsResourceRecord> additionalRecords = new List<DnsResourceRecord>();
|
||||
|
||||
foreach (DnsResourceRecord nsRecord in nsRecords)
|
||||
{
|
||||
if (nsRecord.Type != DnsResourceRecordType.NS)
|
||||
continue;
|
||||
|
||||
AuthZone authZone = _root.FindZone((nsRecord.RDATA as DnsNSRecord).NSDomainName, out _, out _, out _);
|
||||
if ((authZone != null) && !authZone.Disabled)
|
||||
{
|
||||
{
|
||||
IReadOnlyList<DnsResourceRecord> records = authZone.QueryRecords(DnsResourceRecordType.A);
|
||||
if ((records.Count > 0) && (records[0].RDATA is DnsARecord))
|
||||
additionalRecords.AddRange(records);
|
||||
}
|
||||
|
||||
{
|
||||
IReadOnlyList<DnsResourceRecord> records = authZone.QueryRecords(DnsResourceRecordType.AAAA);
|
||||
if ((records.Count > 0) && (records[0].RDATA is DnsAAAARecord))
|
||||
additionalRecords.AddRange(records);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return additionalRecords;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region public
|
||||
|
||||
public bool CreatePrimaryZone(string domain, string masterNameServer, bool @internal)
|
||||
{
|
||||
return _root.TryAdd(new PrimaryZone(domain, new DnsSOARecord(masterNameServer, "hostmaster." + masterNameServer, 1, 14400, 3600, 604800, 900), @internal));
|
||||
}
|
||||
|
||||
public bool CreatePrimaryZone(string domain, DnsSOARecord soaRecord, DnsNSRecord ns, bool @internal)
|
||||
{
|
||||
return _root.TryAdd(new PrimaryZone(domain, soaRecord, ns, @internal));
|
||||
}
|
||||
|
||||
public bool CreateSecondaryZone(string domain, string masterNameServer)
|
||||
{
|
||||
return _root.TryAdd(new SecondaryZone(domain, new DnsSOARecord(masterNameServer, "hostmaster." + masterNameServer, 1, 14400, 3600, 604800, 900)));
|
||||
}
|
||||
|
||||
public bool CreateStubZone(string domain, string masterNameServer)
|
||||
{
|
||||
return _root.TryAdd(new StubZone(domain, new DnsSOARecord(masterNameServer, "hostmaster." + masterNameServer, 1, 14400, 3600, 604800, 900)));
|
||||
}
|
||||
|
||||
public bool DeleteZone(string domain)
|
||||
{
|
||||
return _root.TryRemove(domain, out _);
|
||||
}
|
||||
|
||||
public AuthZoneInfo GetZoneInfo(string domain)
|
||||
{
|
||||
_ = _root.FindZone(domain, out _, out AuthZone authority, out _);
|
||||
if (authority == null)
|
||||
return null;
|
||||
|
||||
return new AuthZoneInfo(authority);
|
||||
}
|
||||
|
||||
public bool ZoneExistsAndEnabled(string domain)
|
||||
{
|
||||
if (_root.TryGet(domain, out AuthZone zone))
|
||||
return !zone.Disabled;
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public void DisableZone(string domain)
|
||||
{
|
||||
if (_root.TryGet(domain, out AuthZone zone))
|
||||
zone.Disabled = true;
|
||||
}
|
||||
|
||||
public List<DnsResourceRecord> ListAllRecords(string domain)
|
||||
{
|
||||
List<DnsResourceRecord> records = new List<DnsResourceRecord>();
|
||||
|
||||
foreach (AuthZone zone in _root.GetZoneWithSubDomainZones(domain))
|
||||
records.AddRange(zone.ListAllRecords());
|
||||
|
||||
return records;
|
||||
}
|
||||
|
||||
public IReadOnlyList<DnsResourceRecord> QueryRecords(string domain, DnsResourceRecordType type)
|
||||
{
|
||||
if (_root.TryGet(domain, out AuthZone zone))
|
||||
return zone.QueryRecords(type);
|
||||
|
||||
return Array.Empty<DnsResourceRecord>();
|
||||
}
|
||||
|
||||
public List<DnsResourceRecord> GetZoneTransferRecords(string domain)
|
||||
{
|
||||
List<DnsResourceRecord> axfrRecords = new List<DnsResourceRecord>();
|
||||
|
||||
List<AuthZone> zones = _root.GetZoneWithSubDomainZones(domain);
|
||||
|
||||
if ((zones.Count > 0) && (zones[0] is PrimaryZone) && !zones[0].Disabled)
|
||||
{
|
||||
//only primary zones support zone transfer
|
||||
DnsResourceRecord soaRecord = zones[0].QueryRecords(DnsResourceRecordType.SOA)[0];
|
||||
|
||||
axfrRecords.Add(soaRecord);
|
||||
|
||||
foreach (Zone zone in zones)
|
||||
{
|
||||
foreach (DnsResourceRecord record in zone.ListAllRecords())
|
||||
{
|
||||
if (record.Type != DnsResourceRecordType.SOA)
|
||||
axfrRecords.Add(record);
|
||||
}
|
||||
}
|
||||
|
||||
axfrRecords.Add(soaRecord);
|
||||
}
|
||||
|
||||
return axfrRecords;
|
||||
}
|
||||
|
||||
public void SetRecords(string domain, DnsResourceRecordType type, uint ttl, DnsResourceRecordData[] records)
|
||||
{
|
||||
AuthZone zone = _root.GetOrAdd(domain, delegate (string key)
|
||||
{
|
||||
return new SubDomainZone(domain);
|
||||
});
|
||||
|
||||
DnsResourceRecord[] resourceRecords = new DnsResourceRecord[records.Length];
|
||||
|
||||
for (int i = 0; i < records.Length; i++)
|
||||
resourceRecords[i] = new DnsResourceRecord(zone.Name, type, DnsClass.IN, ttl, records[i]);
|
||||
|
||||
zone.SetRecords(type, resourceRecords);
|
||||
|
||||
if (zone is SubDomainZone)
|
||||
zone.Disabled = zone.AreAllRecordsDisabled();
|
||||
}
|
||||
|
||||
public void SetRecords(IReadOnlyList<DnsResourceRecord> resourceRecords)
|
||||
{
|
||||
if (resourceRecords.Count == 1)
|
||||
{
|
||||
AuthZone zone = _root.GetOrAdd(resourceRecords[0].Name, delegate (string key)
|
||||
{
|
||||
return new SubDomainZone(resourceRecords[0].Name);
|
||||
});
|
||||
|
||||
zone.SetRecords(resourceRecords[0].Type, resourceRecords);
|
||||
|
||||
if (zone is SubDomainZone)
|
||||
zone.Disabled = zone.AreAllRecordsDisabled();
|
||||
}
|
||||
else
|
||||
{
|
||||
Dictionary<string, Dictionary<DnsResourceRecordType, List<DnsResourceRecord>>> groupedByDomainRecords = DnsResourceRecord.GroupRecords(resourceRecords);
|
||||
|
||||
//add grouped records
|
||||
foreach (KeyValuePair<string, Dictionary<DnsResourceRecordType, List<DnsResourceRecord>>> groupedByTypeRecords in groupedByDomainRecords)
|
||||
{
|
||||
AuthZone zone = _root.GetOrAdd(groupedByTypeRecords.Key, delegate (string key)
|
||||
{
|
||||
return new SubDomainZone(groupedByTypeRecords.Key);
|
||||
});
|
||||
|
||||
foreach (KeyValuePair<DnsResourceRecordType, List<DnsResourceRecord>> groupedRecords in groupedByTypeRecords.Value)
|
||||
zone.SetRecords(groupedRecords.Key, groupedRecords.Value);
|
||||
|
||||
if (zone is SubDomainZone)
|
||||
zone.Disabled = zone.AreAllRecordsDisabled();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void AddRecord(string domain, DnsResourceRecordType type, uint ttl, DnsResourceRecordData record)
|
||||
{
|
||||
AuthZone zone = _root.GetOrAdd(domain, delegate (string key)
|
||||
{
|
||||
return new SubDomainZone(domain);
|
||||
});
|
||||
|
||||
zone.AddRecord(new DnsResourceRecord(zone.Name, type, DnsClass.IN, ttl, record));
|
||||
|
||||
if (zone is SubDomainZone)
|
||||
zone.Disabled = zone.AreAllRecordsDisabled();
|
||||
}
|
||||
|
||||
public void UpdateRecord(DnsResourceRecord oldRecord, DnsResourceRecord newRecord)
|
||||
{
|
||||
if (oldRecord.Type != newRecord.Type)
|
||||
throw new DnsServerException("Cannot update record: new record must be of same type.");
|
||||
|
||||
if (oldRecord.Type == DnsResourceRecordType.SOA)
|
||||
throw new DnsServerException("Cannot update record: use SetRecords() for updating SOA record.");
|
||||
|
||||
if (!_root.TryGet(oldRecord.Name, out AuthZone zone))
|
||||
throw new DnsServerException("Cannot update record: zone does not exists.");
|
||||
|
||||
switch (oldRecord.Type)
|
||||
{
|
||||
case DnsResourceRecordType.CNAME:
|
||||
case DnsResourceRecordType.PTR:
|
||||
if (oldRecord.Name.Equals(newRecord.Name, StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
zone.SetRecords(newRecord.Type, new DnsResourceRecord[] { newRecord });
|
||||
|
||||
if (zone is SubDomainZone)
|
||||
zone.Disabled = zone.AreAllRecordsDisabled();
|
||||
}
|
||||
else
|
||||
{
|
||||
zone.DeleteRecords(oldRecord.Type);
|
||||
|
||||
if (zone is SubDomainZone)
|
||||
{
|
||||
if (zone.IsEmpty)
|
||||
_root.TryRemove(oldRecord.Name, out _); //remove empty sub zone
|
||||
else
|
||||
zone.Disabled = zone.AreAllRecordsDisabled();
|
||||
}
|
||||
|
||||
AuthZone newZone = _root.GetOrAdd(newRecord.Name, delegate (string key)
|
||||
{
|
||||
return new SubDomainZone(newRecord.Name);
|
||||
});
|
||||
|
||||
newZone.SetRecords(newRecord.Type, new DnsResourceRecord[] { newRecord });
|
||||
|
||||
if (newZone is SubDomainZone)
|
||||
newZone.Disabled = zone.AreAllRecordsDisabled();
|
||||
}
|
||||
break;
|
||||
|
||||
default:
|
||||
if (oldRecord.Name.Equals(newRecord.Name, StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
zone.DeleteRecord(oldRecord.Type, oldRecord.RDATA);
|
||||
zone.AddRecord(newRecord);
|
||||
|
||||
if (zone is SubDomainZone)
|
||||
zone.Disabled = zone.AreAllRecordsDisabled();
|
||||
}
|
||||
else
|
||||
{
|
||||
zone.DeleteRecord(oldRecord.Type, oldRecord.RDATA);
|
||||
|
||||
if (zone is SubDomainZone)
|
||||
{
|
||||
if (zone.IsEmpty)
|
||||
_root.TryRemove(oldRecord.Name, out _); //remove empty sub zone
|
||||
else
|
||||
zone.Disabled = zone.AreAllRecordsDisabled();
|
||||
}
|
||||
|
||||
AuthZone newZone = _root.GetOrAdd(newRecord.Name, delegate (string key)
|
||||
{
|
||||
return new SubDomainZone(newRecord.Name);
|
||||
});
|
||||
|
||||
newZone.AddRecord(newRecord);
|
||||
|
||||
if (newZone is SubDomainZone)
|
||||
newZone.Disabled = zone.AreAllRecordsDisabled();
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
public void DeleteRecord(string domain, DnsResourceRecordType type, DnsResourceRecordData record)
|
||||
{
|
||||
if (_root.TryGet(domain, out AuthZone zone))
|
||||
{
|
||||
zone.DeleteRecord(type, record);
|
||||
|
||||
if (zone is SubDomainZone)
|
||||
{
|
||||
if (zone.IsEmpty)
|
||||
_root.TryRemove(domain, out _); //remove empty sub zone
|
||||
else
|
||||
zone.Disabled = zone.AreAllRecordsDisabled();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void DeleteRecords(string domain, DnsResourceRecordType type)
|
||||
{
|
||||
if (_root.TryGet(domain, out AuthZone zone))
|
||||
{
|
||||
zone.DeleteRecords(type);
|
||||
|
||||
if (zone is SubDomainZone)
|
||||
{
|
||||
if (zone.IsEmpty)
|
||||
_root.TryRemove(domain, out _); //remove empty sub zone
|
||||
else
|
||||
zone.Disabled = zone.AreAllRecordsDisabled();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public List<AuthZoneInfo> ListZones()
|
||||
{
|
||||
List<AuthZoneInfo> zones = new List<AuthZoneInfo>();
|
||||
|
||||
foreach (AuthZone zone in _root)
|
||||
{
|
||||
AuthZoneInfo zoneInfo = new AuthZoneInfo(zone);
|
||||
switch (zoneInfo.Type)
|
||||
{
|
||||
case AuthZoneType.Primary:
|
||||
case AuthZoneType.Secondary:
|
||||
case AuthZoneType.Stub:
|
||||
zones.Add(zoneInfo);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return zones;
|
||||
}
|
||||
|
||||
public List<string> ListSubDomains(string domain)
|
||||
{
|
||||
return _root.ListSubDomains(domain);
|
||||
}
|
||||
|
||||
public DnsDatagram Query(DnsDatagram request)
|
||||
{
|
||||
AuthZone zone = _root.FindZone(request.Question[0].Name, out AuthZone delegation, out AuthZone authZone, out bool hasSubDomains);
|
||||
|
||||
if ((authZone == null) || authZone.Disabled) //no authority for requested zone
|
||||
return new DnsDatagram(request.Identifier, true, DnsOpcode.StandardQuery, false, false, request.RecursionDesired, false, false, false, DnsResponseCode.Refused, request.Question);
|
||||
|
||||
if ((authZone is StubZone) || ((delegation != null) && !delegation.Disabled))
|
||||
{
|
||||
//zone is delegation
|
||||
IReadOnlyList<DnsResourceRecord> authority = delegation.QueryRecords(DnsResourceRecordType.NS);
|
||||
IReadOnlyList<DnsResourceRecord> additional = GetAdditionalRecords(authority);
|
||||
|
||||
return new DnsDatagram(request.Identifier, true, DnsOpcode.StandardQuery, false, false, request.RecursionDesired, false, false, false, DnsResponseCode.NoError, request.Question, null, authority, additional);
|
||||
}
|
||||
|
||||
if ((zone == null) || zone.Disabled)
|
||||
{
|
||||
//zone not found
|
||||
DnsResponseCode rCode = hasSubDomains ? DnsResponseCode.NoError : DnsResponseCode.NameError;
|
||||
IReadOnlyList<DnsResourceRecord> authority = authZone.QueryRecords(DnsResourceRecordType.SOA);
|
||||
|
||||
return new DnsDatagram(request.Identifier, true, DnsOpcode.StandardQuery, true, false, request.RecursionDesired, false, false, false, rCode, request.Question, null, authority);
|
||||
}
|
||||
|
||||
//zone found
|
||||
if ((authZone is PrimaryZone) || (authZone is SecondaryZone))
|
||||
{
|
||||
IReadOnlyList<DnsResourceRecord> authority;
|
||||
IReadOnlyList<DnsResourceRecord> additional;
|
||||
|
||||
IReadOnlyList<DnsResourceRecord> answers = zone.QueryRecords(request.Question[0].Type);
|
||||
if (answers.Count == 0)
|
||||
{
|
||||
//record type not found
|
||||
authority = authZone.QueryRecords(DnsResourceRecordType.SOA);
|
||||
additional = null;
|
||||
}
|
||||
else
|
||||
{
|
||||
//record type found
|
||||
if (zone.Name.Contains("*"))
|
||||
{
|
||||
//wildcard zone; generate new answer records
|
||||
DnsResourceRecord[] wildcardAnswers = new DnsResourceRecord[answers.Count];
|
||||
|
||||
for (int i = 0; i < answers.Count; i++)
|
||||
wildcardAnswers[i] = new DnsResourceRecord(request.Question[0].Name, answers[i].Type, answers[i].Class, answers[i].TtlValue, answers[i].RDATA) { Tag = answers[i].Tag };
|
||||
|
||||
answers = wildcardAnswers;
|
||||
}
|
||||
|
||||
switch (request.Question[0].Type)
|
||||
{
|
||||
case DnsResourceRecordType.NS:
|
||||
authority = null;
|
||||
additional = GetAdditionalRecords(answers);
|
||||
break;
|
||||
|
||||
case DnsResourceRecordType.ANY:
|
||||
authority = null;
|
||||
additional = null;
|
||||
break;
|
||||
|
||||
default:
|
||||
authority = authZone.QueryRecords(DnsResourceRecordType.NS);
|
||||
additional = GetAdditionalRecords(authority);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return new DnsDatagram(request.Identifier, true, DnsOpcode.StandardQuery, true, false, request.RecursionDesired, false, false, false, DnsResponseCode.NoError, request.Question, answers, authority, additional);
|
||||
}
|
||||
|
||||
//unknown zone type encountered
|
||||
return new DnsDatagram(request.Identifier, true, DnsOpcode.StandardQuery, false, false, request.RecursionDesired, false, false, false, DnsResponseCode.NotImplemented, request.Question);
|
||||
}
|
||||
|
||||
public void LoadZoneFrom(Stream s)
|
||||
{
|
||||
BinaryReader bR = new BinaryReader(s);
|
||||
|
||||
if (Encoding.ASCII.GetString(bR.ReadBytes(2)) != "DZ")
|
||||
throw new InvalidDataException("DnsServer zone file format is invalid.");
|
||||
|
||||
switch (bR.ReadByte())
|
||||
{
|
||||
case 2:
|
||||
{
|
||||
DnsResourceRecord[] records = new DnsResourceRecord[bR.ReadInt32()];
|
||||
if (records.Length > 0)
|
||||
{
|
||||
for (int i = 0; i < records.Length; i++)
|
||||
records[i] = new DnsResourceRecord(s);
|
||||
|
||||
//make zone info
|
||||
AuthZoneInfo zoneInfo = new AuthZoneInfo(records[0].Name, AuthZoneType.Primary, false);
|
||||
|
||||
//create zone
|
||||
CreateZone(zoneInfo);
|
||||
|
||||
//set records
|
||||
SetRecords(records);
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
case 3:
|
||||
{
|
||||
bool zoneDisabled = bR.ReadBoolean();
|
||||
DnsResourceRecord[] records = new DnsResourceRecord[bR.ReadInt32()];
|
||||
if (records.Length > 0)
|
||||
{
|
||||
for (int i = 0; i < records.Length; i++)
|
||||
{
|
||||
records[i] = new DnsResourceRecord(s);
|
||||
records[i].Tag = new DnsResourceRecordInfo(bR);
|
||||
}
|
||||
|
||||
//make zone info
|
||||
AuthZoneInfo zoneInfo = new AuthZoneInfo(records[0].Name, AuthZoneType.Primary, zoneDisabled);
|
||||
|
||||
//create zone
|
||||
CreateZone(zoneInfo);
|
||||
|
||||
//set records
|
||||
SetRecords(records);
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
case 4:
|
||||
{
|
||||
//read zone info
|
||||
AuthZoneInfo zoneInfo = new AuthZoneInfo(bR);
|
||||
|
||||
//create zone
|
||||
CreateZone(zoneInfo);
|
||||
|
||||
//read all zone records
|
||||
DnsResourceRecord[] records = new DnsResourceRecord[bR.ReadInt32()];
|
||||
if (records.Length > 0)
|
||||
{
|
||||
for (int i = 0; i < records.Length; i++)
|
||||
{
|
||||
records[i] = new DnsResourceRecord(s);
|
||||
records[i].Tag = new DnsResourceRecordInfo(bR);
|
||||
}
|
||||
|
||||
//set records
|
||||
SetRecords(records);
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
default:
|
||||
throw new InvalidDataException("DNS Zone file version not supported.");
|
||||
}
|
||||
}
|
||||
|
||||
public void WriteZoneTo(string domain, Stream s)
|
||||
{
|
||||
List<AuthZone> zones = _root.GetZoneWithSubDomainZones(domain);
|
||||
if (zones.Count == 0)
|
||||
throw new DnsServerException("Zone was not found: " + domain);
|
||||
|
||||
//serialize zone
|
||||
BinaryWriter bW = new BinaryWriter(s);
|
||||
|
||||
bW.Write(Encoding.ASCII.GetBytes("DZ")); //format
|
||||
bW.Write((byte)4); //version
|
||||
|
||||
//write zone info
|
||||
AuthZoneInfo zoneInfo = new AuthZoneInfo(zones[0]);
|
||||
zoneInfo.WriteTo(bW);
|
||||
|
||||
//write all zone records
|
||||
List<DnsResourceRecord> records = new List<DnsResourceRecord>();
|
||||
|
||||
foreach (AuthZone zone in zones)
|
||||
records.AddRange(zone.ListAllRecords());
|
||||
|
||||
bW.Write(records.Count);
|
||||
|
||||
foreach (DnsResourceRecord record in records)
|
||||
{
|
||||
record.WriteTo(s);
|
||||
|
||||
DnsResourceRecordInfo rrInfo = record.Tag as DnsResourceRecordInfo;
|
||||
if (rrInfo == null)
|
||||
rrInfo = new DnsResourceRecordInfo(); //default info
|
||||
|
||||
rrInfo.WriteTo(bW);
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user