/* 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 . */ using DnsServerCore.Dns.ResourceRecords; using System; using System.Collections.Generic; using System.Linq; using System.Net; using System.Threading.Tasks; using TechnitiumLibrary; using TechnitiumLibrary.Net; using TechnitiumLibrary.Net.Dns; using TechnitiumLibrary.Net.Dns.ResourceRecords; namespace DnsServerCore.Dns.Zones { class SecondaryCatalogZone : SecondaryForwarderZone { #region events public event EventHandler ZoneAdded; public event EventHandler ZoneRemoved; #endregion #region variables readonly static IReadOnlyCollection _allowACL = [ new NetworkAccessControl(IPAddress.Any, 0), new NetworkAccessControl(IPAddress.IPv6Any, 0) ]; readonly static IReadOnlyCollection _queryAccessAllowOnlyPrivateNetworksACL = [ 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) ]; readonly static IReadOnlyCollection _allowOnlyZoneNameServersACL = [ new NetworkAccessControl(IPAddress.Parse("224.0.0.0"), 32) ]; readonly static IReadOnlyCollection _denyACL = [ new NetworkAccessControl(IPAddress.Parse("127.0.0.0"), 8), new NetworkAccessControl(IPAddress.Parse("::1"), 128) ]; readonly static NetworkAccessControl _allowZoneNameServersAndUseSpecifiedNetworkACL = new NetworkAccessControl(IPAddress.Parse("224.0.0.0"), 32); Dictionary _membersIndex = new Dictionary(); #endregion #region constructor public SecondaryCatalogZone(DnsServer dnsServer, AuthZoneInfo zoneInfo) : base(dnsServer, zoneInfo) { } public SecondaryCatalogZone(DnsServer dnsServer, string name, IReadOnlyList primaryNameServerAddresses, DnsTransportProtocol primaryZoneTransferProtocol = DnsTransportProtocol.Tcp, string primaryZoneTransferTsigKeyName = null) : base(dnsServer, name, primaryNameServerAddresses, primaryZoneTransferProtocol, primaryZoneTransferTsigKeyName) { } #endregion #region protected protected override void InitZone() { //init secondary catalog zone with dummy SOA and NS records DnsSOARecordData soa = new DnsSOARecordData("invalid", "invalid", 0, 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"))]; } #endregion #region internal internal void BuildMembersIndex() { Dictionary membersIndex = new Dictionary(); foreach (KeyValuePair memberEntry in EnumerateCatalogMemberZones(_dnsServer)) membersIndex.TryAdd(memberEntry.Key.ToLowerInvariant(), memberEntry.Value); _membersIndex = membersIndex; } #endregion #region secondary catalog public IReadOnlyCollection GetAllMemberZoneNames() { return _membersIndex.Keys; } protected override async Task FinalizeZoneTransferAsync() { //secondary catalog does not maintain zone history; no need to call base method string version = GetVersion(); if ((version is null) || !version.Equals("2", StringComparison.OrdinalIgnoreCase)) { _dnsServer.LogManager?.Write("Failed to provision Secondary Catalog zone '" + ToString() + "': catalog version not supported."); return; } Dictionary updatedMembersIndex = new Dictionary(); foreach (KeyValuePair memberEntry in EnumerateCatalogMemberZones(_dnsServer)) updatedMembersIndex.TryAdd(memberEntry.Key, memberEntry.Value); Dictionary membersToRemove = new Dictionary(); Dictionary membersToAdd = new Dictionary(); foreach (KeyValuePair memberEntry in _membersIndex) { if (!updatedMembersIndex.TryGetValue(memberEntry.Key, out string updatedMembersZoneDomain)) { //member was removed from catalog zone; remove local zone membersToRemove.Add(memberEntry.Key, null); } else if (!memberEntry.Value.Equals(updatedMembersZoneDomain, StringComparison.OrdinalIgnoreCase)) { //member exists but label does not match; reprovision zone membersToRemove.Add(memberEntry.Key, null); membersToAdd.Add(memberEntry.Key, updatedMembersZoneDomain); } } foreach (KeyValuePair updatedMemberEntry in updatedMembersIndex) { if (_membersIndex.TryGetValue(updatedMemberEntry.Key, out _)) { ApexZone apexZone = _dnsServer.AuthZoneManager.GetApexZone(updatedMemberEntry.Key); if (apexZone is not null) continue; //zone already exists; do nothing } //member was added to catalog zone; provision zone membersToAdd.TryAdd(updatedMemberEntry.Key, updatedMemberEntry.Value); } //set global custom properties UpdateGlobalAllowQueryProperty(); UpdateGlobalAllowTransferAndTsigKeyNamesProperties(); //add and remove member zones if ((membersToRemove.Count > 0) || (membersToAdd.Count > 0)) await AddAndRemoveMemberZonesAsync(membersToRemove, membersToAdd); //set member zone custom properties if (updatedMembersIndex.Count > 0) UpdateMemberZoneCustomProperties(updatedMembersIndex); _membersIndex = updatedMembersIndex; _dnsServer.AuthZoneManager.SaveZoneFile(_name); } protected override async Task FinalizeIncrementalZoneTransferAsync(IReadOnlyList historyRecords) { //secondary catalog does not maintain zone history; no need to call base method string version = GetVersion(); if ((version is null) || !version.Equals("2", StringComparison.OrdinalIgnoreCase)) { _dnsServer.LogManager?.Write("Failed to provision Secondary Catalog zone '" + ToString() + "': catalog version not supported."); return; } bool isAddHistoryRecord = false; bool updateGlobalAllowQueryProperty = false; bool updateGlobalAllowTransferAndTsigKeyNamesProperties = false; Dictionary membersToRemove = new Dictionary(); Dictionary membersToAdd = new Dictionary(); Dictionary membersToUpdate = new Dictionary(); //inspect records in history for (int i = 1; i < historyRecords.Count; i++) { DnsResourceRecord historyRecord = historyRecords[i]; if (historyRecord.Type == DnsResourceRecordType.SOA) { isAddHistoryRecord = true; //removed records completed continue; } if (historyRecord.Name.Length == _name.Length) continue; //skip apex records string subdomain = historyRecord.Name.Substring(0, historyRecord.Name.Length - _name.Length - 1).ToLowerInvariant(); string[] labels = subdomain.Split('.'); Array.Reverse(labels); switch (labels[0]) { case "ext": if (labels.Length > 1) { switch (labels[1]) { case "allow-query": updateGlobalAllowQueryProperty = true; break; case "allow-transfer": case "transfer-tsig-key-names": updateGlobalAllowTransferAndTsigKeyNamesProperties = true; break; } } break; case "zones": if (labels.Length == 2) { if (historyRecord.Type == DnsResourceRecordType.PTR) { string memberZoneName = (historyRecord.RDATA as DnsPTRRecordData).Domain.ToLowerInvariant(); if (isAddHistoryRecord) { string memberZoneDomain = subdomain + "." + _name; membersToAdd.TryAdd(memberZoneName, memberZoneDomain); membersToUpdate.TryAdd(memberZoneName, memberZoneDomain); } else { membersToRemove.TryAdd(memberZoneName, null); } } } else if (labels.Length > 2) { switch (labels[2]) { case "ext": case "coo": string memberZoneDomain = labels[1] + "." + labels[0] + "." + _name; DnsResourceRecord prevHistoryRecord = historyRecords[i - 1]; if (prevHistoryRecord.Name.EndsWith(memberZoneDomain)) break; //skip since its same member zone's custom property IReadOnlyList ptrRecords = _dnsServer.AuthZoneManager.GetRecords(_name, memberZoneDomain, DnsResourceRecordType.PTR); if (ptrRecords.Count > 0) membersToUpdate.TryAdd((ptrRecords[0].RDATA as DnsPTRRecordData).Domain.ToLowerInvariant(), memberZoneDomain); break; } } break; } } //apply changes if (updateGlobalAllowQueryProperty) UpdateGlobalAllowQueryProperty(); if (updateGlobalAllowTransferAndTsigKeyNamesProperties) UpdateGlobalAllowTransferAndTsigKeyNamesProperties(); if ((membersToRemove.Count > 0) || (membersToAdd.Count > 0)) await AddAndRemoveMemberZonesAsync(membersToRemove, membersToAdd); if (membersToUpdate.Count > 0) UpdateMemberZoneCustomProperties(membersToUpdate); if ((membersToRemove.Count > 0) || (membersToAdd.Count > 0)) { //update members index Dictionary updatedMembersIndex = new Dictionary(_membersIndex); foreach (KeyValuePair removedMember in membersToRemove) updatedMembersIndex.Remove(removedMember.Key); foreach (KeyValuePair addedMember in membersToAdd) updatedMembersIndex.TryAdd(addedMember.Key, addedMember.Value); _membersIndex = updatedMembersIndex; } _dnsServer.AuthZoneManager.SaveZoneFile(_name); } private async Task AddAndRemoveMemberZonesAsync(Dictionary membersToRemove, Dictionary membersToAdd) { //remove zones foreach (KeyValuePair removeMember in membersToRemove) { ApexZone apexZone = _dnsServer.AuthZoneManager.GetApexZone(removeMember.Key); if ((apexZone is not null) && _name.Equals(apexZone.CatalogZoneName, StringComparison.OrdinalIgnoreCase)) DeleteMemberZone(apexZone); } //add zones List> addZoneTasks = new List>(); foreach (KeyValuePair addMember in membersToAdd) { ApexZone apexZone = _dnsServer.AuthZoneManager.GetApexZone(addMember.Key); if (apexZone is null) { //create zone AuthZoneType zoneType = GetZoneTypeProperty(addMember.Value); switch (zoneType) { case AuthZoneType.Primary: { //create secondary zone IReadOnlyList primaryNameServerAddresses; DnsTransportProtocol primaryZoneTransferProtocol; string primaryZoneTransferTsigKeyName; IReadOnlyList> primaries = GetPrimariesProperty(addMember.Value); if (primaries.Count == 0) primaries = GetPrimariesProperty(_name); if (primaries.Count == 0) { primaryNameServerAddresses = PrimaryNameServerAddresses; primaryZoneTransferProtocol = PrimaryZoneTransferProtocol; primaryZoneTransferTsigKeyName = PrimaryZoneTransferTsigKeyName; } else { Tuple primary = primaries[0]; primaryNameServerAddresses = [new NameServerAddress(primary.Item1, DnsTransportProtocol.Tcp)]; primaryZoneTransferProtocol = DnsTransportProtocol.Tcp; primaryZoneTransferTsigKeyName = primary.Item2; } addZoneTasks.Add(_dnsServer.AuthZoneManager.CreateSecondaryZoneAsync(addMember.Key, primaryNameServerAddresses, primaryZoneTransferProtocol, primaryZoneTransferTsigKeyName, false, true)); } break; case AuthZoneType.Stub: { //create stub zone IReadOnlyList primaryNameServerAddresses = GetPrimaryAddressesProperty(addMember.Value); addZoneTasks.Add(_dnsServer.AuthZoneManager.CreateStubZoneAsync(addMember.Key, primaryNameServerAddresses, true)); } break; case AuthZoneType.Forwarder: { //create secondary forwarder zone addZoneTasks.Add(Task.FromResult(_dnsServer.AuthZoneManager.CreateSecondaryForwarderZone(addMember.Key, PrimaryNameServerAddresses, PrimaryZoneTransferProtocol, PrimaryZoneTransferTsigKeyName))); } break; } } } await Task.WhenAll(addZoneTasks); //finalize add zone tasks foreach (Task task in addZoneTasks) { try { AuthZoneInfo zoneInfo = await task; //set as catalog zone member zoneInfo.ApexZone.CatalogZoneName = _name; //raise event ZoneAdded?.Invoke(this, new SecondaryCatalogEventArgs(zoneInfo)); //write log _dnsServer.LogManager?.Write(zoneInfo.TypeName + " zone '" + zoneInfo.DisplayName + "' was added via Secondary Catalog zone '" + ToString() + "' sucessfully."); } catch (Exception ex) { _dnsServer.LogManager?.Write(ex); } } } private void UpdateGlobalAllowQueryProperty() { //allow query global custom property IReadOnlyCollection globalAllowQueryACL = GetAllowQueryProperty(_name); if (globalAllowQueryACL.Count > 0) { _queryAccess = GetQueryAccessType(globalAllowQueryACL); switch (_queryAccess) { case AuthZoneQueryAccess.UseSpecifiedNetworkACL: QueryAccessNetworkACL = globalAllowQueryACL; break; case AuthZoneQueryAccess.AllowZoneNameServersAndUseSpecifiedNetworkACL: QueryAccessNetworkACL = GetFilteredACL(globalAllowQueryACL); break; default: QueryAccessNetworkACL = null; break; } } else { _queryAccess = AuthZoneQueryAccess.Allow; QueryAccessNetworkACL = null; } } private void UpdateGlobalAllowTransferAndTsigKeyNamesProperties() { //allow transfer global custom property IReadOnlyCollection globalAllowTransferACL = GetAllowTransferProperty(_name); if (globalAllowTransferACL.Count > 0) { _zoneTransfer = GetZoneTransferType(globalAllowTransferACL); switch (_zoneTransfer) { case AuthZoneTransfer.UseSpecifiedNetworkACL: ZoneTransferNetworkACL = globalAllowTransferACL; break; case AuthZoneTransfer.AllowZoneNameServersAndUseSpecifiedNetworkACL: ZoneTransferNetworkACL = GetFilteredACL(globalAllowTransferACL); break; default: ZoneTransferNetworkACL = null; break; } //zone tranfer tsig key names global custom property ZoneTransferTsigKeyNames = GetZoneTransferTsigKeyNamesProperty(_name); } else { _zoneTransfer = AuthZoneTransfer.Deny; ZoneTransferNetworkACL = null; ZoneTransferTsigKeyNames = null; } } private void UpdateMemberZoneCustomProperties(Dictionary membersToUpdate) { foreach (KeyValuePair updatedMemberEntry in membersToUpdate) { ApexZone memberApexZone = _dnsServer.AuthZoneManager.GetApexZone(updatedMemberEntry.Key); if ((memberApexZone is not null) && _name.Equals(memberApexZone.CatalogZoneName, StringComparison.OrdinalIgnoreCase)) { //change of ownership property { string newCatalogZoneName = GetChangeOfOwnershipProperty(updatedMemberEntry.Value); if (newCatalogZoneName is not null) { ApexZone catalogApexZone = _dnsServer.AuthZoneManager.GetApexZone(newCatalogZoneName); if (catalogApexZone is SecondaryCatalogZone secondaryCatalogZone) { //found secondary catalog zone; transfer ownership to it memberApexZone.CatalogZoneName = secondaryCatalogZone._name; } else { //no such secondary catalog zone exists; delete member zone DeleteMemberZone(memberApexZone); continue; } } } //allow query member zone custom property { IReadOnlyCollection allowQueryACL = GetAllowQueryProperty(updatedMemberEntry.Value); if (allowQueryACL.Count > 0) { memberApexZone.QueryAccess = GetQueryAccessType(allowQueryACL); switch (memberApexZone.QueryAccess) { case AuthZoneQueryAccess.UseSpecifiedNetworkACL: memberApexZone.QueryAccessNetworkACL = allowQueryACL; break; case AuthZoneQueryAccess.AllowZoneNameServersAndUseSpecifiedNetworkACL: memberApexZone.QueryAccessNetworkACL = GetFilteredACL(allowQueryACL); break; default: memberApexZone.QueryAccessNetworkACL = null; break; } memberApexZone.OverrideCatalogQueryAccess = true; } else { memberApexZone.OverrideCatalogQueryAccess = false; memberApexZone.QueryAccess = AuthZoneQueryAccess.Allow; memberApexZone.QueryAccessNetworkACL = null; } } if (memberApexZone is StubZone stubZone) { //primary addresses property stubZone.PrimaryNameServerAddresses = GetPrimaryAddressesProperty(updatedMemberEntry.Value); } else if (memberApexZone is SecondaryForwarderZone) { //do nothing } else if (memberApexZone is SecondaryZone secondaryZone) { //primaries property IReadOnlyList> primaries = GetPrimariesProperty(updatedMemberEntry.Value); if (primaries.Count == 0) primaries = GetPrimariesProperty(_name); if (primaries.Count > 0) { Tuple primary = primaries[0]; secondaryZone.PrimaryNameServerAddresses = [new NameServerAddress(primary.Item1, DnsTransportProtocol.Tcp)]; secondaryZone.PrimaryZoneTransferProtocol = DnsTransportProtocol.Tcp; secondaryZone.PrimaryZoneTransferTsigKeyName = primary.Item2; secondaryZone.OverrideCatalogPrimaryNameServers = true; } else { secondaryZone.OverrideCatalogPrimaryNameServers = false; secondaryZone.PrimaryNameServerAddresses = null; secondaryZone.PrimaryZoneTransferProtocol = DnsTransportProtocol.Tcp; secondaryZone.PrimaryZoneTransferTsigKeyName = null; } //allow transfer member zone custom property IReadOnlyCollection allowTransferACL = GetAllowTransferProperty(updatedMemberEntry.Value); if (allowTransferACL.Count > 0) { memberApexZone.ZoneTransfer = GetZoneTransferType(allowTransferACL); switch (memberApexZone.ZoneTransfer) { case AuthZoneTransfer.UseSpecifiedNetworkACL: memberApexZone.ZoneTransferNetworkACL = allowTransferACL; break; case AuthZoneTransfer.AllowZoneNameServersAndUseSpecifiedNetworkACL: memberApexZone.ZoneTransferNetworkACL = GetFilteredACL(allowTransferACL); break; default: memberApexZone.ZoneTransferNetworkACL = null; break; } //zone tranfer tsig key names member zone custom property memberApexZone.ZoneTransferTsigKeyNames = GetZoneTransferTsigKeyNamesProperty(updatedMemberEntry.Value); memberApexZone.OverrideCatalogZoneTransfer = true; } else { memberApexZone.OverrideCatalogZoneTransfer = false; memberApexZone.ZoneTransfer = AuthZoneTransfer.Deny; memberApexZone.ZoneTransferNetworkACL = null; memberApexZone.ZoneTransferTsigKeyNames = null; } } _dnsServer.AuthZoneManager.SaveZoneFile(memberApexZone.Name); } } } private void DeleteMemberZone(ApexZone apexZone) { AuthZoneInfo zoneInfo = new AuthZoneInfo(apexZone); if (_dnsServer.AuthZoneManager.DeleteZone(zoneInfo, true)) { ZoneRemoved?.Invoke(this, new SecondaryCatalogEventArgs(zoneInfo)); _dnsServer.LogManager?.Write(apexZone.GetZoneTypeName() + " zone '" + apexZone.ToString() + "' was removed via Secondary Catalog zone '" + ToString() + "' sucessfully."); } } private string GetVersion() { string domain = "version." + _name; IReadOnlyList records = _dnsServer.AuthZoneManager.GetRecords(_name, domain, DnsResourceRecordType.TXT); if (records.Count > 0) return (records[0].RDATA as DnsTXTRecordData).GetText(); return null; } private string GetChangeOfOwnershipProperty(string memberZoneDomain) { string domain = "coo." + memberZoneDomain; IReadOnlyList records = _dnsServer.AuthZoneManager.GetRecords(_name, domain, DnsResourceRecordType.PTR); if (records.Count > 0) return (records[0].RDATA as DnsPTRRecordData).Domain; return null; } private AuthZoneType GetZoneTypeProperty(string memberZoneDomain) { string domain = "zone-type.ext." + memberZoneDomain; IReadOnlyList records = _dnsServer.AuthZoneManager.GetRecords(_name, domain, DnsResourceRecordType.TXT); if (records.Count > 0) return Enum.Parse((records[0].RDATA as DnsTXTRecordData).GetText(), true); return AuthZoneType.Primary; } private List> GetPrimariesProperty(string memberZoneDomain) { string domain = "primaries.ext." + memberZoneDomain; List> primaries = new List>(2); AuthZone authZone = _dnsServer.AuthZoneManager.GetAuthZone(_name, domain); if (authZone is not null) { foreach (DnsResourceRecord record in authZone.GetRecords(DnsResourceRecordType.A)) primaries.Add(new Tuple((record.RDATA as DnsARecordData).Address, null)); foreach (DnsResourceRecord record in authZone.GetRecords(DnsResourceRecordType.AAAA)) primaries.Add(new Tuple((record.RDATA as DnsAAAARecordData).Address, null)); } List subdomains = new List(); _dnsServer.AuthZoneManager.ListSubDomains(domain, subdomains); foreach (string subdomain in subdomains) { AuthZone subZone = _dnsServer.AuthZoneManager.GetAuthZone(_name, subdomain + "." + domain); if (subZone is null) continue; string tsigKeyName = null; IReadOnlyList szTXTRecords = subZone.GetRecords(DnsResourceRecordType.TXT); if (szTXTRecords.Count > 0) tsigKeyName = (szTXTRecords[0].RDATA as DnsTXTRecordData).GetText(); foreach (DnsResourceRecord record in subZone.GetRecords(DnsResourceRecordType.A)) primaries.Add(new Tuple((record.RDATA as DnsARecordData).Address, tsigKeyName)); foreach (DnsResourceRecord record in subZone.GetRecords(DnsResourceRecordType.AAAA)) primaries.Add(new Tuple((record.RDATA as DnsAAAARecordData).Address, tsigKeyName)); } return primaries; } private IReadOnlyList GetPrimaryAddressesProperty(string memberZoneDomain) { string domain = "primary-addresses.ext." + memberZoneDomain; IReadOnlyList records = _dnsServer.AuthZoneManager.GetRecords(_name, domain, DnsResourceRecordType.TXT); if (records.Count > 0) return (records[0].RDATA as DnsTXTRecordData).CharacterStrings.Convert(NameServerAddress.Parse); return []; } private IReadOnlyCollection GetAllowQueryProperty(string memberZoneDomain) { string domain = "allow-query.ext." + memberZoneDomain; IReadOnlyList records = _dnsServer.AuthZoneManager.GetRecords(_name, domain, DnsResourceRecordType.APL); if (records.Count > 0) return NetworkAccessControl.ConvertFromAPLRecordData(records[0].RDATA as DnsAPLRecordData); return []; } private IReadOnlyCollection GetAllowTransferProperty(string memberZoneDomain) { string domain = "allow-transfer.ext." + memberZoneDomain; IReadOnlyList records = _dnsServer.AuthZoneManager.GetRecords(_name, domain, DnsResourceRecordType.APL); if (records.Count > 0) return NetworkAccessControl.ConvertFromAPLRecordData(records[0].RDATA as DnsAPLRecordData); return []; } private Dictionary GetZoneTransferTsigKeyNamesProperty(string memberZoneDomain) { string domain = "transfer-tsig-key-names.ext." + memberZoneDomain; IReadOnlyList records = _dnsServer.AuthZoneManager.GetRecords(_name, domain, DnsResourceRecordType.PTR); Dictionary keyNames = new Dictionary(records.Count); foreach (DnsResourceRecord record in records) keyNames.TryAdd((record.RDATA as DnsPTRRecordData).Domain.ToLowerInvariant(), null); return keyNames; } private static AuthZoneQueryAccess GetQueryAccessType(IReadOnlyCollection acl) { if (acl.HasSameItems(_allowACL)) return AuthZoneQueryAccess.Allow; if (acl.HasSameItems(_queryAccessAllowOnlyPrivateNetworksACL)) return AuthZoneQueryAccess.AllowOnlyPrivateNetworks; if (acl.HasSameItems(_allowOnlyZoneNameServersACL)) return AuthZoneQueryAccess.AllowOnlyZoneNameServers; if ((acl.Count > 1) && acl.Contains(_allowZoneNameServersAndUseSpecifiedNetworkACL)) return AuthZoneQueryAccess.AllowZoneNameServersAndUseSpecifiedNetworkACL; if (acl.HasSameItems(_denyACL)) return AuthZoneQueryAccess.Deny; return AuthZoneQueryAccess.UseSpecifiedNetworkACL; } private static AuthZoneTransfer GetZoneTransferType(IReadOnlyCollection acl) { if (acl.HasSameItems(_allowACL)) return AuthZoneTransfer.Allow; if (acl.HasSameItems(_allowOnlyZoneNameServersACL)) return AuthZoneTransfer.AllowOnlyZoneNameServers; if ((acl.Count > 1) && acl.Contains(_allowZoneNameServersAndUseSpecifiedNetworkACL)) return AuthZoneTransfer.AllowZoneNameServersAndUseSpecifiedNetworkACL; if (acl.HasSameItems(_denyACL)) return AuthZoneTransfer.Deny; return AuthZoneTransfer.UseSpecifiedNetworkACL; } private static List GetFilteredACL(IReadOnlyCollection acl) { List filteredACL = new List(acl.Count); foreach (NetworkAccessControl ac in acl) { if (ac.Equals(_allowZoneNameServersAndUseSpecifiedNetworkACL)) continue; filteredACL.Add(ac); } return filteredACL; } #endregion #region public public override string GetZoneTypeName() { return "Secondary Catalog"; } public override IReadOnlyList QueryRecords(DnsResourceRecordType type, bool dnssecOk) { return []; //secondary 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 { throw new InvalidOperationException(); } set { throw new InvalidOperationException(); } } public override AuthZoneQueryAccess QueryAccess { get { return _queryAccess; } set { throw new InvalidOperationException(); } } public override AuthZoneTransfer ZoneTransfer { get { return _zoneTransfer; } set { throw new InvalidOperationException(); } } public override AuthZoneUpdate Update { get { return base.Update; } set { throw new InvalidOperationException(); } } #endregion } public class SecondaryCatalogEventArgs : EventArgs { #region variables readonly AuthZoneInfo _zoneInfo; #endregion #region constructor public SecondaryCatalogEventArgs(AuthZoneInfo zoneInfo) { _zoneInfo = zoneInfo; } #endregion #region properties public AuthZoneInfo ZoneInfo { get { return _zoneInfo; } } #endregion } }