123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421 |
- /*
- 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.Linq;
- using System.Security.Cryptography;
- using System.Threading;
- using TechnitiumLibrary;
- using TechnitiumLibrary.Net;
- using TechnitiumLibrary.Net.Dns;
- using TechnitiumLibrary.Net.Dns.ResourceRecords;
- namespace DnsServerCore.Dns.Zones
- {
- class CatalogZone : ForwarderZone
- {
- #region variables
- readonly Dictionary<string, string> _membersIndex = new Dictionary<string, string>();
- readonly ReaderWriterLockSlim _membersIndexLock = new ReaderWriterLockSlim();
- #endregion
- #region constructor
- public CatalogZone(DnsServer dnsServer, AuthZoneInfo zoneInfo)
- : base(dnsServer, zoneInfo)
- { }
- public CatalogZone(DnsServer dnsServer, string name)
- : base(dnsServer, name)
- { }
- #endregion
- #region IDisposable
- protected override void Dispose(bool disposing)
- {
- try
- {
- _membersIndexLock.Dispose();
- }
- finally
- {
- base.Dispose(disposing);
- }
- }
- #endregion
- #region internal
- internal override void InitZone()
- {
- //init catalog zone with dummy SOA and NS records
- DnsSOARecordData soa = new DnsSOARecordData("invalid", "invalid", 1, 300, 60, 604800, 900);
- DnsResourceRecord soaRecord = new DnsResourceRecord(_name, DnsResourceRecordType.SOA, DnsClass.IN, 0, soa);
- soaRecord.GetAuthGenericRecordInfo().LastModified = DateTime.UtcNow;
- _entries[DnsResourceRecordType.SOA] = [soaRecord];
- _entries[DnsResourceRecordType.NS] = [new DnsResourceRecord(_name, DnsResourceRecordType.NS, DnsClass.IN, 0, new DnsNSRecordData("invalid"))];
- }
- internal void InitZoneProperties()
- {
- //set catalog zone version record
- _dnsServer.AuthZoneManager.SetRecord(_name, new DnsResourceRecord("version." + _name, DnsResourceRecordType.TXT, DnsClass.IN, 0, new DnsTXTRecordData("2")));
- //init catalog global properties
- QueryAccess = AuthZoneQueryAccess.Allow;
- ZoneTransfer = AuthZoneTransfer.Deny;
- }
- internal void BuildMembersIndex()
- {
- foreach (KeyValuePair<string, string> memberEntry in EnumerateCatalogMemberZones(_dnsServer))
- _membersIndex.TryAdd(memberEntry.Key.ToLowerInvariant(), memberEntry.Value);
- }
- #endregion
- #region catalog
- public void AddMemberZone(string memberZoneName, AuthZoneType zoneType)
- {
- memberZoneName = memberZoneName.ToLowerInvariant();
- _membersIndexLock.EnterWriteLock();
- try
- {
- if (_membersIndex.TryGetValue(memberZoneName, out _))
- {
- if (_membersIndex.Remove(memberZoneName, out string removedMemberZoneDomain))
- {
- foreach (DnsResourceRecord record in _dnsServer.AuthZoneManager.EnumerateAllRecords(_name, removedMemberZoneDomain, true))
- _dnsServer.AuthZoneManager.DeleteRecord(_name, record);
- }
- }
- string memberZoneDomain = GetDomainWithLabel("zones." + _name);
- DateTime utcNow = DateTime.UtcNow;
- DnsResourceRecord ptrRecord = new DnsResourceRecord(memberZoneDomain, DnsResourceRecordType.PTR, DnsClass.IN, 0, new DnsPTRRecordData(memberZoneName));
- ptrRecord.GetAuthGenericRecordInfo().LastModified = utcNow;
- DnsResourceRecord txtRecord = new DnsResourceRecord("zone-type.ext." + memberZoneDomain, DnsResourceRecordType.TXT, DnsClass.IN, 0, new DnsTXTRecordData(zoneType.ToString().ToLowerInvariant()));
- txtRecord.GetAuthGenericRecordInfo().LastModified = utcNow;
- _dnsServer.AuthZoneManager.AddRecord(_name, ptrRecord);
- _dnsServer.AuthZoneManager.AddRecord(_name, txtRecord);
- _membersIndex[memberZoneName] = memberZoneDomain;
- }
- finally
- {
- _membersIndexLock.ExitWriteLock();
- }
- }
- public bool RemoveMemberZone(string memberZoneName)
- {
- memberZoneName = memberZoneName.ToLowerInvariant();
- _membersIndexLock.EnterWriteLock();
- try
- {
- if (_membersIndex.Remove(memberZoneName, out string removedMemberZoneDomain))
- {
- foreach (DnsResourceRecord record in _dnsServer.AuthZoneManager.EnumerateAllRecords(_name, removedMemberZoneDomain, true))
- _dnsServer.AuthZoneManager.DeleteRecord(_name, record);
- return true;
- }
- return false;
- }
- finally
- {
- _membersIndexLock.ExitWriteLock();
- }
- }
- public void ChangeMemberZoneOwnership(string memberZoneName, string newCatalogZoneName)
- {
- string memberZoneDomain = GetMemberZoneDomain(memberZoneName);
- string domain = "coo." + memberZoneDomain;
- DateTime utcNow = DateTime.UtcNow;
- uint soaExpiry = GetZoneSoaExpire();
- //add COO record with expiry
- DnsResourceRecord cooRecord = new DnsResourceRecord(domain, DnsResourceRecordType.PTR, DnsClass.IN, 0, new DnsPTRRecordData(newCatalogZoneName));
- GenericRecordInfo cooRecordInfo = cooRecord.GetAuthGenericRecordInfo();
- cooRecordInfo.LastModified = utcNow;
- cooRecordInfo.ExpiryTtl = soaExpiry;
- _dnsServer.AuthZoneManager.SetRecord(_name, cooRecord);
- //set expiry for other member zone records
- foreach (DnsResourceRecord record in _dnsServer.AuthZoneManager.EnumerateAllRecords(_name, memberZoneDomain, true))
- {
- GenericRecordInfo recordInfo = record.GetAuthGenericRecordInfo();
- recordInfo.LastModified = utcNow;
- recordInfo.ExpiryTtl = soaExpiry;
- }
- }
- public IReadOnlyCollection<string> GetAllMemberZoneNames()
- {
- _membersIndexLock.EnterReadLock();
- try
- {
- return _membersIndex.Keys.ToArray();
- }
- finally
- {
- _membersIndexLock.ExitReadLock();
- }
- }
- public void SetAllowQueryProperty(IReadOnlyCollection<NetworkAccessControl> acl = null, string memberZoneName = null)
- {
- string domain = "allow-query.ext." + GetMemberZoneDomain(memberZoneName);
- if (acl is null)
- {
- _dnsServer.AuthZoneManager.DeleteRecords(_name, domain, DnsResourceRecordType.APL);
- }
- else
- {
- DnsResourceRecord record = new DnsResourceRecord(domain, DnsResourceRecordType.APL, DnsClass.IN, 0, NetworkAccessControl.ConvertToAPLRecordData(acl));
- record.GetAuthGenericRecordInfo().LastModified = DateTime.UtcNow;
- _dnsServer.AuthZoneManager.SetRecord(_name, record);
- }
- }
- public void SetAllowTransferProperty(IReadOnlyCollection<NetworkAccessControl> acl = null, string memberZoneName = null)
- {
- string domain = "allow-transfer.ext." + GetMemberZoneDomain(memberZoneName);
- if (acl is null)
- {
- _dnsServer.AuthZoneManager.DeleteRecords(_name, domain, DnsResourceRecordType.APL);
- }
- else
- {
- DnsResourceRecord record = new DnsResourceRecord(domain, DnsResourceRecordType.APL, DnsClass.IN, 0, NetworkAccessControl.ConvertToAPLRecordData(acl));
- record.GetAuthGenericRecordInfo().LastModified = DateTime.UtcNow;
- _dnsServer.AuthZoneManager.SetRecord(_name, record);
- }
- }
- public void SetZoneTransferTsigKeyNamesProperty(IReadOnlyDictionary<string, object> tsigKeyNames = null, string memberZoneName = null)
- {
- string domain = "transfer-tsig-key-names.ext." + GetMemberZoneDomain(memberZoneName);
- if (tsigKeyNames is null)
- {
- _dnsServer.AuthZoneManager.DeleteRecords(_name, domain, DnsResourceRecordType.PTR);
- }
- else
- {
- DnsResourceRecord[] records = new DnsResourceRecord[tsigKeyNames.Count];
- int i = 0;
- foreach (KeyValuePair<string, object> entry in tsigKeyNames)
- {
- DnsResourceRecord record = new DnsResourceRecord(domain, DnsResourceRecordType.PTR, DnsClass.IN, 0, new DnsPTRRecordData(entry.Key));
- record.GetAuthGenericRecordInfo().LastModified = DateTime.UtcNow;
- records[i++] = record;
- }
- _dnsServer.AuthZoneManager.SetRecords(_name, records);
- }
- }
- public void SetPrimaryAddressesProperty(IReadOnlyList<NameServerAddress> primaryServerAddresses = null, string memberZoneName = null)
- {
- string domain = "primary-addresses.ext." + GetMemberZoneDomain(memberZoneName);
- if (primaryServerAddresses is null)
- {
- _dnsServer.AuthZoneManager.DeleteRecords(_name, domain, DnsResourceRecordType.TXT);
- }
- else
- {
- IReadOnlyList<string> charStrings = primaryServerAddresses.Convert(delegate (NameServerAddress nameServer)
- {
- return nameServer.ToString();
- });
- DnsResourceRecord record = new DnsResourceRecord(domain, DnsResourceRecordType.TXT, DnsClass.IN, 0, new DnsTXTRecordData(charStrings));
- record.GetAuthGenericRecordInfo().LastModified = DateTime.UtcNow;
- _dnsServer.AuthZoneManager.SetRecord(_name, record);
- }
- }
- private string GetMemberZoneDomain(string memberZoneName = null)
- {
- if (memberZoneName is null)
- {
- return _name;
- }
- else
- {
- memberZoneName = memberZoneName.ToLowerInvariant();
- _membersIndexLock.EnterReadLock();
- try
- {
- if (!_membersIndex.TryGetValue(memberZoneName, out string memberZoneDomain))
- throw new DnsServerException("Failed to find '" + memberZoneName + "' member zone entry in '" + ToString() + "' Catalog zone: member zone does not exists.");
- return memberZoneDomain;
- }
- finally
- {
- _membersIndexLock.ExitReadLock();
- }
- }
- }
- private string GetDomainWithLabel(string domain)
- {
- Span<byte> buffer = stackalloc byte[8];
- int i = 0;
- do
- {
- RandomNumberGenerator.Fill(buffer);
- string label = Base32.ToBase32HexString(buffer, true).ToLowerInvariant();
- string domainWithLabel = label + "." + domain;
- if (_dnsServer.AuthZoneManager.NameExists(_name, domainWithLabel))
- continue;
- return domainWithLabel;
- }
- while (++i < 10);
- throw new DnsServerException("Failed to generate unique label for the given domain name '" + domain + "'. Please try again.");
- }
- #endregion
- #region public
- public override string GetZoneTypeName()
- {
- return "Catalog";
- }
- public override void SetRecords(DnsResourceRecordType type, IReadOnlyList<DnsResourceRecord> records)
- {
- switch (type)
- {
- case DnsResourceRecordType.SOA:
- if ((records.Count != 1) || !records[0].Name.Equals(_name, StringComparison.OrdinalIgnoreCase))
- throw new InvalidOperationException("Invalid SOA record.");
- DnsResourceRecord newSoaRecord = records[0];
- DnsSOARecordData newSoa = newSoaRecord.RDATA as DnsSOARecordData;
- //reset fixed record values
- DnsSOARecordData modifiedSoa = new DnsSOARecordData("invalid", "invalid", newSoa.Serial, newSoa.Refresh, newSoa.Retry, newSoa.Expire, newSoa.Minimum);
- DnsResourceRecord modifiedSoaRecord = new DnsResourceRecord(_name, DnsResourceRecordType.SOA, DnsClass.IN, 0, modifiedSoa) { Tag = newSoaRecord.Tag };
- base.SetRecords(type, [modifiedSoaRecord]);
- break;
- default:
- throw new InvalidOperationException("Cannot set records in Catalog zone.");
- }
- }
- public override void AddRecord(DnsResourceRecord record)
- {
- throw new InvalidOperationException("Cannot add record in Catalog zone.");
- }
- public override bool DeleteRecords(DnsResourceRecordType type)
- {
- throw new InvalidOperationException("Cannot delete record in Catalog zone.");
- }
- public override bool DeleteRecord(DnsResourceRecordType type, DnsResourceRecordData record)
- {
- throw new InvalidOperationException("Cannot delete records in Catalog zone.");
- }
- public override void UpdateRecord(DnsResourceRecord oldRecord, DnsResourceRecord newRecord)
- {
- throw new InvalidOperationException("Cannot update record in Catalog zone.");
- }
- public override IReadOnlyList<DnsResourceRecord> QueryRecords(DnsResourceRecordType type, bool dnssecOk)
- {
- return []; //catalog zone is not queriable
- }
- #endregion
- #region properties
- public override string CatalogZoneName
- {
- get { return base.CatalogZoneName; }
- set { throw new InvalidOperationException(); }
- }
- public override bool OverrideCatalogQueryAccess
- {
- get { return base.OverrideCatalogQueryAccess; }
- set { throw new InvalidOperationException(); }
- }
- public override bool OverrideCatalogZoneTransfer
- {
- get { return base.OverrideCatalogZoneTransfer; }
- set { throw new InvalidOperationException(); }
- }
- public override bool OverrideCatalogNotify
- {
- get { return base.OverrideCatalogNotify; }
- set { throw new InvalidOperationException(); }
- }
- public override AuthZoneUpdate Update
- {
- get { return base.Update; }
- set { throw new InvalidOperationException(); }
- }
- #endregion
- }
- }
|