diff --git a/DnsServerCore/Dns/Zones/SecondaryCatalogZone.cs b/DnsServerCore/Dns/Zones/SecondaryCatalogZone.cs
new file mode 100644
index 00000000..e8b2968f
--- /dev/null
+++ b/DnsServerCore/Dns/Zones/SecondaryCatalogZone.cs
@@ -0,0 +1,652 @@
+/*
+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
+ await ReProvisionZonesAsync();
+ }
+
+ protected override async Task FinalizeIncrementalZoneTransferAsync(IReadOnlyList historyRecords)
+ {
+ //secondary catalog does not maintain zone history
+ await ReProvisionZonesAsync();
+ }
+
+ private async Task ReProvisionZonesAsync()
+ {
+ 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 _))
+ {
+ AuthZone authZone = _dnsServer.AuthZoneManager.GetAuthZone(updatedMemberEntry.Key, updatedMemberEntry.Key);
+ if (authZone is ApexZone)
+ continue; //zone already exists; do nothing
+ }
+
+ //member was added to catalog zone; provision zone
+ membersToAdd.TryAdd(updatedMemberEntry.Key, updatedMemberEntry.Value);
+ }
+
+ //remove zones
+ foreach (KeyValuePair removeMember in membersToRemove)
+ {
+ AuthZone authZone = _dnsServer.AuthZoneManager.GetAuthZone(removeMember.Key, removeMember.Key);
+ if ((authZone is ApexZone apexZone) && _name.Equals(apexZone.CatalogZoneName, StringComparison.OrdinalIgnoreCase))
+ DeleteMemberZone(apexZone);
+ }
+
+ //add zones
+ List> addZoneTasks = new List>();
+
+ foreach (KeyValuePair addMember in membersToAdd)
+ {
+ AuthZone authZone = _dnsServer.AuthZoneManager.GetAuthZone(addMember.Key, addMember.Key);
+ if (authZone is not ApexZone)
+ {
+ //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));
+ }
+ 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);
+ }
+ }
+
+ //set properties for all members
+ foreach (KeyValuePair updatedMemberEntry in updatedMembersIndex)
+ {
+ AuthZone authZone = _dnsServer.AuthZoneManager.GetAuthZone(updatedMemberEntry.Key, updatedMemberEntry.Key);
+ if (authZone is ApexZone apexZone && _name.Equals(apexZone.CatalogZoneName, StringComparison.OrdinalIgnoreCase))
+ {
+ //change of ownership property
+ {
+ string newCatalogZoneName = GetChangeOfOwnershipProperty(updatedMemberEntry.Value);
+ if (newCatalogZoneName is not null)
+ {
+ AuthZone catalogAuthZone = _dnsServer.AuthZoneManager.GetAuthZone(newCatalogZoneName, newCatalogZoneName);
+ if (catalogAuthZone is SecondaryCatalogZone secondaryCatalogZone)
+ {
+ //found secondary catalog zone; transfer ownership to it
+ apexZone.CatalogZoneName = secondaryCatalogZone._name;
+ }
+ else
+ {
+ //no such secondary catalog zone exists; delete member zone
+ DeleteMemberZone(apexZone);
+ continue;
+ }
+ }
+ }
+
+ //allow query property
+ {
+ IReadOnlyCollection allowQueryACL = GetAllowQueryProperty(updatedMemberEntry.Value);
+ if (allowQueryACL.Count == 0)
+ allowQueryACL = GetAllowQueryProperty(_name);
+
+ apexZone.QueryAccess = GetQueryAccessType(allowQueryACL);
+
+ switch (apexZone.QueryAccess)
+ {
+ case AuthZoneQueryAccess.UseSpecifiedNetworkACL:
+ apexZone.QueryAccessNetworkACL = allowQueryACL;
+ break;
+
+ case AuthZoneQueryAccess.AllowZoneNameServersAndUseSpecifiedNetworkACL:
+ apexZone.QueryAccessNetworkACL = GetFilteredACL(allowQueryACL);
+ break;
+
+ default:
+ apexZone.QueryAccessNetworkACL = null;
+ break;
+ }
+ }
+
+ if (apexZone is StubZone stubZone)
+ {
+ //primary addresses property
+ IReadOnlyList primaryNameServerAddresses = GetPrimaryAddressesProperty(updatedMemberEntry.Value);
+
+ stubZone.PrimaryNameServerAddresses = primaryNameServerAddresses;
+ }
+ else if (apexZone is SecondaryForwarderZone)
+ {
+ //do nothing
+ }
+ else if (apexZone 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 property
+ {
+ IReadOnlyCollection allowTransferACL = GetAllowTransferProperty(updatedMemberEntry.Value);
+ if (allowTransferACL.Count == 0)
+ allowTransferACL = GetAllowTransferProperty(_name);
+
+ apexZone.ZoneTransfer = GetZoneTransferType(allowTransferACL);
+
+ switch (apexZone.ZoneTransfer)
+ {
+ case AuthZoneTransfer.UseSpecifiedNetworkACL:
+ apexZone.ZoneTransferNetworkACL = allowTransferACL;
+ break;
+
+ case AuthZoneTransfer.AllowZoneNameServersAndUseSpecifiedNetworkACL:
+ apexZone.ZoneTransferNetworkACL = GetFilteredACL(allowTransferACL);
+ break;
+
+ default:
+ apexZone.ZoneTransferNetworkACL = null;
+ break;
+ }
+ }
+
+ //zone tranfer tsig key names property
+ {
+ IReadOnlyDictionary tsigKeyNames = GetZoneTransferTsigKeyNamesProperty(updatedMemberEntry.Value);
+ if (tsigKeyNames.Count == 0)
+ tsigKeyNames = GetZoneTransferTsigKeyNamesProperty(_name);
+
+ apexZone.ZoneTransferTsigKeyNames = tsigKeyNames;
+ }
+ }
+
+ _dnsServer.AuthZoneManager.SaveZoneFile(apexZone.Name);
+ }
+ }
+
+ _membersIndex = updatedMembersIndex;
+ }
+
+ 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 AuthZoneQueryAccess QueryAccess
+ {
+ get { return base.QueryAccess; }
+ 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
+ }
+}