SecondaryCatalogZone.cs 37 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866
  1. /*
  2. Technitium DNS Server
  3. Copyright (C) 2024 Shreyas Zare (shreyas@technitium.com)
  4. This program is free software: you can redistribute it and/or modify
  5. it under the terms of the GNU General Public License as published by
  6. the Free Software Foundation, either version 3 of the License, or
  7. (at your option) any later version.
  8. This program is distributed in the hope that it will be useful,
  9. but WITHOUT ANY WARRANTY; without even the implied warranty of
  10. MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  11. GNU General Public License for more details.
  12. You should have received a copy of the GNU General Public License
  13. along with this program. If not, see <http://www.gnu.org/licenses/>.
  14. */
  15. using DnsServerCore.Dns.ResourceRecords;
  16. using System;
  17. using System.Collections.Generic;
  18. using System.Linq;
  19. using System.Net;
  20. using System.Threading.Tasks;
  21. using TechnitiumLibrary;
  22. using TechnitiumLibrary.Net;
  23. using TechnitiumLibrary.Net.Dns;
  24. using TechnitiumLibrary.Net.Dns.ResourceRecords;
  25. namespace DnsServerCore.Dns.Zones
  26. {
  27. class SecondaryCatalogZone : SecondaryForwarderZone
  28. {
  29. #region events
  30. public event EventHandler<SecondaryCatalogEventArgs> ZoneAdded;
  31. public event EventHandler<SecondaryCatalogEventArgs> ZoneRemoved;
  32. #endregion
  33. #region variables
  34. readonly static IReadOnlyCollection<NetworkAccessControl> _allowACL =
  35. [
  36. new NetworkAccessControl(IPAddress.Any, 0),
  37. new NetworkAccessControl(IPAddress.IPv6Any, 0)
  38. ];
  39. readonly static IReadOnlyCollection<NetworkAccessControl> _queryAccessAllowOnlyPrivateNetworksACL =
  40. [
  41. new NetworkAccessControl(IPAddress.Parse("127.0.0.0"), 8),
  42. new NetworkAccessControl(IPAddress.Parse("10.0.0.0"), 8),
  43. new NetworkAccessControl(IPAddress.Parse("100.64.0.0"), 10),
  44. new NetworkAccessControl(IPAddress.Parse("169.254.0.0"), 16),
  45. new NetworkAccessControl(IPAddress.Parse("172.16.0.0"), 12),
  46. new NetworkAccessControl(IPAddress.Parse("192.168.0.0"), 16),
  47. new NetworkAccessControl(IPAddress.Parse("2000::"), 3, true),
  48. new NetworkAccessControl(IPAddress.IPv6Any, 0)
  49. ];
  50. readonly static IReadOnlyCollection<NetworkAccessControl> _allowOnlyZoneNameServersACL =
  51. [
  52. new NetworkAccessControl(IPAddress.Parse("224.0.0.0"), 32)
  53. ];
  54. readonly static IReadOnlyCollection<NetworkAccessControl> _denyACL =
  55. [
  56. new NetworkAccessControl(IPAddress.Parse("127.0.0.0"), 8),
  57. new NetworkAccessControl(IPAddress.Parse("::1"), 128)
  58. ];
  59. readonly static NetworkAccessControl _allowZoneNameServersAndUseSpecifiedNetworkACL = new NetworkAccessControl(IPAddress.Parse("224.0.0.0"), 32);
  60. Dictionary<string, string> _membersIndex = new Dictionary<string, string>();
  61. #endregion
  62. #region constructor
  63. public SecondaryCatalogZone(DnsServer dnsServer, AuthZoneInfo zoneInfo)
  64. : base(dnsServer, zoneInfo)
  65. { }
  66. public SecondaryCatalogZone(DnsServer dnsServer, string name, IReadOnlyList<NameServerAddress> primaryNameServerAddresses, DnsTransportProtocol primaryZoneTransferProtocol = DnsTransportProtocol.Tcp, string primaryZoneTransferTsigKeyName = null)
  67. : base(dnsServer, name, primaryNameServerAddresses, primaryZoneTransferProtocol, primaryZoneTransferTsigKeyName)
  68. { }
  69. #endregion
  70. #region protected
  71. protected override void InitZone()
  72. {
  73. //init secondary catalog zone with dummy SOA and NS records
  74. DnsSOARecordData soa = new DnsSOARecordData("invalid", "invalid", 0, 300, 60, 604800, 900);
  75. DnsResourceRecord soaRecord = new DnsResourceRecord(_name, DnsResourceRecordType.SOA, DnsClass.IN, 0, soa);
  76. soaRecord.GetAuthGenericRecordInfo().LastModified = DateTime.UtcNow;
  77. _entries[DnsResourceRecordType.SOA] = [soaRecord];
  78. _entries[DnsResourceRecordType.NS] = [new DnsResourceRecord(_name, DnsResourceRecordType.NS, DnsClass.IN, 0, new DnsNSRecordData("invalid"))];
  79. }
  80. #endregion
  81. #region internal
  82. internal void BuildMembersIndex()
  83. {
  84. Dictionary<string, string> membersIndex = new Dictionary<string, string>();
  85. foreach (KeyValuePair<string, string> memberEntry in EnumerateCatalogMemberZones(_dnsServer))
  86. membersIndex.TryAdd(memberEntry.Key.ToLowerInvariant(), memberEntry.Value);
  87. _membersIndex = membersIndex;
  88. }
  89. #endregion
  90. #region secondary catalog
  91. public IReadOnlyCollection<string> GetAllMemberZoneNames()
  92. {
  93. return _membersIndex.Keys;
  94. }
  95. protected override async Task FinalizeZoneTransferAsync()
  96. {
  97. //secondary catalog does not maintain zone history; no need to call base method
  98. string version = GetVersion();
  99. if ((version is null) || !version.Equals("2", StringComparison.OrdinalIgnoreCase))
  100. {
  101. _dnsServer.LogManager?.Write("Failed to provision Secondary Catalog zone '" + ToString() + "': catalog version not supported.");
  102. return;
  103. }
  104. Dictionary<string, string> updatedMembersIndex = new Dictionary<string, string>();
  105. foreach (KeyValuePair<string, string> memberEntry in EnumerateCatalogMemberZones(_dnsServer))
  106. updatedMembersIndex.TryAdd(memberEntry.Key, memberEntry.Value);
  107. Dictionary<string, object> membersToRemove = new Dictionary<string, object>();
  108. Dictionary<string, string> membersToAdd = new Dictionary<string, string>();
  109. foreach (KeyValuePair<string, string> memberEntry in _membersIndex)
  110. {
  111. if (!updatedMembersIndex.TryGetValue(memberEntry.Key, out string updatedMembersZoneDomain))
  112. {
  113. //member was removed from catalog zone; remove local zone
  114. membersToRemove.Add(memberEntry.Key, null);
  115. }
  116. else if (!memberEntry.Value.Equals(updatedMembersZoneDomain, StringComparison.OrdinalIgnoreCase))
  117. {
  118. //member exists but label does not match; reprovision zone
  119. membersToRemove.Add(memberEntry.Key, null);
  120. membersToAdd.Add(memberEntry.Key, updatedMembersZoneDomain);
  121. }
  122. }
  123. foreach (KeyValuePair<string, string> updatedMemberEntry in updatedMembersIndex)
  124. {
  125. if (_membersIndex.TryGetValue(updatedMemberEntry.Key, out _))
  126. {
  127. ApexZone apexZone = _dnsServer.AuthZoneManager.GetApexZone(updatedMemberEntry.Key);
  128. if (apexZone is not null)
  129. continue; //zone already exists; do nothing
  130. }
  131. //member was added to catalog zone; provision zone
  132. membersToAdd.TryAdd(updatedMemberEntry.Key, updatedMemberEntry.Value);
  133. }
  134. //set global custom properties
  135. UpdateGlobalAllowQueryProperty();
  136. UpdateGlobalAllowTransferAndTsigKeyNamesProperties();
  137. //add and remove member zones
  138. if ((membersToRemove.Count > 0) || (membersToAdd.Count > 0))
  139. await AddAndRemoveMemberZonesAsync(membersToRemove, membersToAdd);
  140. //set member zone custom properties
  141. if (updatedMembersIndex.Count > 0)
  142. UpdateMemberZoneCustomProperties(updatedMembersIndex);
  143. _membersIndex = updatedMembersIndex;
  144. _dnsServer.AuthZoneManager.SaveZoneFile(_name);
  145. }
  146. protected override async Task FinalizeIncrementalZoneTransferAsync(IReadOnlyList<DnsResourceRecord> historyRecords)
  147. {
  148. //secondary catalog does not maintain zone history; no need to call base method
  149. string version = GetVersion();
  150. if ((version is null) || !version.Equals("2", StringComparison.OrdinalIgnoreCase))
  151. {
  152. _dnsServer.LogManager?.Write("Failed to provision Secondary Catalog zone '" + ToString() + "': catalog version not supported.");
  153. return;
  154. }
  155. bool isAddHistoryRecord = false;
  156. bool updateGlobalAllowQueryProperty = false;
  157. bool updateGlobalAllowTransferAndTsigKeyNamesProperties = false;
  158. Dictionary<string, object> membersToRemove = new Dictionary<string, object>();
  159. Dictionary<string, string> membersToAdd = new Dictionary<string, string>();
  160. Dictionary<string, string> membersToUpdate = new Dictionary<string, string>();
  161. //inspect records in history
  162. for (int i = 1; i < historyRecords.Count; i++)
  163. {
  164. DnsResourceRecord historyRecord = historyRecords[i];
  165. if (historyRecord.Type == DnsResourceRecordType.SOA)
  166. {
  167. isAddHistoryRecord = true; //removed records completed
  168. continue;
  169. }
  170. if (historyRecord.Name.Length == _name.Length)
  171. continue; //skip apex records
  172. string subdomain = historyRecord.Name.Substring(0, historyRecord.Name.Length - _name.Length - 1).ToLowerInvariant();
  173. string[] labels = subdomain.Split('.');
  174. Array.Reverse(labels);
  175. switch (labels[0])
  176. {
  177. case "ext":
  178. if (labels.Length > 1)
  179. {
  180. switch (labels[1])
  181. {
  182. case "allow-query":
  183. updateGlobalAllowQueryProperty = true;
  184. break;
  185. case "allow-transfer":
  186. case "transfer-tsig-key-names":
  187. updateGlobalAllowTransferAndTsigKeyNamesProperties = true;
  188. break;
  189. }
  190. }
  191. break;
  192. case "zones":
  193. if (labels.Length == 2)
  194. {
  195. if (historyRecord.Type == DnsResourceRecordType.PTR)
  196. {
  197. string memberZoneName = (historyRecord.RDATA as DnsPTRRecordData).Domain.ToLowerInvariant();
  198. if (isAddHistoryRecord)
  199. {
  200. string memberZoneDomain = subdomain + "." + _name;
  201. membersToAdd.TryAdd(memberZoneName, memberZoneDomain);
  202. membersToUpdate.TryAdd(memberZoneName, memberZoneDomain);
  203. }
  204. else
  205. {
  206. membersToRemove.TryAdd(memberZoneName, null);
  207. }
  208. }
  209. }
  210. else if (labels.Length > 2)
  211. {
  212. switch (labels[2])
  213. {
  214. case "ext":
  215. case "coo":
  216. string memberZoneDomain = labels[1] + "." + labels[0] + "." + _name;
  217. DnsResourceRecord prevHistoryRecord = historyRecords[i - 1];
  218. if (prevHistoryRecord.Name.EndsWith(memberZoneDomain))
  219. break; //skip since its same member zone's custom property
  220. IReadOnlyList<DnsResourceRecord> ptrRecords = _dnsServer.AuthZoneManager.GetRecords(_name, memberZoneDomain, DnsResourceRecordType.PTR);
  221. if (ptrRecords.Count > 0)
  222. membersToUpdate.TryAdd((ptrRecords[0].RDATA as DnsPTRRecordData).Domain.ToLowerInvariant(), memberZoneDomain);
  223. break;
  224. }
  225. }
  226. break;
  227. }
  228. }
  229. //apply changes
  230. if (updateGlobalAllowQueryProperty)
  231. UpdateGlobalAllowQueryProperty();
  232. if (updateGlobalAllowTransferAndTsigKeyNamesProperties)
  233. UpdateGlobalAllowTransferAndTsigKeyNamesProperties();
  234. if ((membersToRemove.Count > 0) || (membersToAdd.Count > 0))
  235. await AddAndRemoveMemberZonesAsync(membersToRemove, membersToAdd);
  236. if (membersToUpdate.Count > 0)
  237. UpdateMemberZoneCustomProperties(membersToUpdate);
  238. if ((membersToRemove.Count > 0) || (membersToAdd.Count > 0))
  239. {
  240. //update members index
  241. Dictionary<string, string> updatedMembersIndex = new Dictionary<string, string>(_membersIndex);
  242. foreach (KeyValuePair<string, object> removedMember in membersToRemove)
  243. updatedMembersIndex.Remove(removedMember.Key);
  244. foreach (KeyValuePair<string, string> addedMember in membersToAdd)
  245. updatedMembersIndex.TryAdd(addedMember.Key, addedMember.Value);
  246. _membersIndex = updatedMembersIndex;
  247. }
  248. _dnsServer.AuthZoneManager.SaveZoneFile(_name);
  249. }
  250. private async Task AddAndRemoveMemberZonesAsync(Dictionary<string, object> membersToRemove, Dictionary<string, string> membersToAdd)
  251. {
  252. //remove zones
  253. foreach (KeyValuePair<string, object> removeMember in membersToRemove)
  254. {
  255. ApexZone apexZone = _dnsServer.AuthZoneManager.GetApexZone(removeMember.Key);
  256. if ((apexZone is not null) && _name.Equals(apexZone.CatalogZoneName, StringComparison.OrdinalIgnoreCase))
  257. DeleteMemberZone(apexZone);
  258. }
  259. //add zones
  260. List<Task<AuthZoneInfo>> addZoneTasks = new List<Task<AuthZoneInfo>>();
  261. foreach (KeyValuePair<string, string> addMember in membersToAdd)
  262. {
  263. ApexZone apexZone = _dnsServer.AuthZoneManager.GetApexZone(addMember.Key);
  264. if (apexZone is null)
  265. {
  266. //create zone
  267. AuthZoneType zoneType = GetZoneTypeProperty(addMember.Value);
  268. switch (zoneType)
  269. {
  270. case AuthZoneType.Primary:
  271. {
  272. //create secondary zone
  273. IReadOnlyList<NameServerAddress> primaryNameServerAddresses;
  274. DnsTransportProtocol primaryZoneTransferProtocol;
  275. string primaryZoneTransferTsigKeyName;
  276. IReadOnlyList<Tuple<IPAddress, string>> primaries = GetPrimariesProperty(addMember.Value);
  277. if (primaries.Count == 0)
  278. primaries = GetPrimariesProperty(_name);
  279. if (primaries.Count == 0)
  280. {
  281. primaryNameServerAddresses = PrimaryNameServerAddresses;
  282. primaryZoneTransferProtocol = PrimaryZoneTransferProtocol;
  283. primaryZoneTransferTsigKeyName = PrimaryZoneTransferTsigKeyName;
  284. }
  285. else
  286. {
  287. Tuple<IPAddress, string> primary = primaries[0];
  288. primaryNameServerAddresses = [new NameServerAddress(primary.Item1, DnsTransportProtocol.Tcp)];
  289. primaryZoneTransferProtocol = DnsTransportProtocol.Tcp;
  290. primaryZoneTransferTsigKeyName = primary.Item2;
  291. }
  292. addZoneTasks.Add(_dnsServer.AuthZoneManager.CreateSecondaryZoneAsync(addMember.Key, primaryNameServerAddresses, primaryZoneTransferProtocol, primaryZoneTransferTsigKeyName, false, true));
  293. }
  294. break;
  295. case AuthZoneType.Stub:
  296. {
  297. //create stub zone
  298. IReadOnlyList<NameServerAddress> primaryNameServerAddresses = GetPrimaryAddressesProperty(addMember.Value);
  299. addZoneTasks.Add(_dnsServer.AuthZoneManager.CreateStubZoneAsync(addMember.Key, primaryNameServerAddresses, true));
  300. }
  301. break;
  302. case AuthZoneType.Forwarder:
  303. {
  304. //create secondary forwarder zone
  305. addZoneTasks.Add(Task.FromResult(_dnsServer.AuthZoneManager.CreateSecondaryForwarderZone(addMember.Key, PrimaryNameServerAddresses, PrimaryZoneTransferProtocol, PrimaryZoneTransferTsigKeyName)));
  306. }
  307. break;
  308. }
  309. }
  310. }
  311. await Task.WhenAll(addZoneTasks);
  312. //finalize add zone tasks
  313. foreach (Task<AuthZoneInfo> task in addZoneTasks)
  314. {
  315. try
  316. {
  317. AuthZoneInfo zoneInfo = await task;
  318. //set as catalog zone member
  319. zoneInfo.ApexZone.CatalogZoneName = _name;
  320. //raise event
  321. ZoneAdded?.Invoke(this, new SecondaryCatalogEventArgs(zoneInfo));
  322. //write log
  323. _dnsServer.LogManager?.Write(zoneInfo.TypeName + " zone '" + zoneInfo.DisplayName + "' was added via Secondary Catalog zone '" + ToString() + "' sucessfully.");
  324. }
  325. catch (Exception ex)
  326. {
  327. _dnsServer.LogManager?.Write(ex);
  328. }
  329. }
  330. }
  331. private void UpdateGlobalAllowQueryProperty()
  332. {
  333. //allow query global custom property
  334. IReadOnlyCollection<NetworkAccessControl> globalAllowQueryACL = GetAllowQueryProperty(_name);
  335. if (globalAllowQueryACL.Count > 0)
  336. {
  337. _queryAccess = GetQueryAccessType(globalAllowQueryACL);
  338. switch (_queryAccess)
  339. {
  340. case AuthZoneQueryAccess.UseSpecifiedNetworkACL:
  341. QueryAccessNetworkACL = globalAllowQueryACL;
  342. break;
  343. case AuthZoneQueryAccess.AllowZoneNameServersAndUseSpecifiedNetworkACL:
  344. QueryAccessNetworkACL = GetFilteredACL(globalAllowQueryACL);
  345. break;
  346. default:
  347. QueryAccessNetworkACL = null;
  348. break;
  349. }
  350. }
  351. else
  352. {
  353. _queryAccess = AuthZoneQueryAccess.Allow;
  354. QueryAccessNetworkACL = null;
  355. }
  356. }
  357. private void UpdateGlobalAllowTransferAndTsigKeyNamesProperties()
  358. {
  359. //allow transfer global custom property
  360. IReadOnlyCollection<NetworkAccessControl> globalAllowTransferACL = GetAllowTransferProperty(_name);
  361. if (globalAllowTransferACL.Count > 0)
  362. {
  363. _zoneTransfer = GetZoneTransferType(globalAllowTransferACL);
  364. switch (_zoneTransfer)
  365. {
  366. case AuthZoneTransfer.UseSpecifiedNetworkACL:
  367. ZoneTransferNetworkACL = globalAllowTransferACL;
  368. break;
  369. case AuthZoneTransfer.AllowZoneNameServersAndUseSpecifiedNetworkACL:
  370. ZoneTransferNetworkACL = GetFilteredACL(globalAllowTransferACL);
  371. break;
  372. default:
  373. ZoneTransferNetworkACL = null;
  374. break;
  375. }
  376. //zone tranfer tsig key names global custom property
  377. ZoneTransferTsigKeyNames = GetZoneTransferTsigKeyNamesProperty(_name);
  378. }
  379. else
  380. {
  381. _zoneTransfer = AuthZoneTransfer.Deny;
  382. ZoneTransferNetworkACL = null;
  383. ZoneTransferTsigKeyNames = null;
  384. }
  385. }
  386. private void UpdateMemberZoneCustomProperties(Dictionary<string, string> membersToUpdate)
  387. {
  388. foreach (KeyValuePair<string, string> updatedMemberEntry in membersToUpdate)
  389. {
  390. ApexZone memberApexZone = _dnsServer.AuthZoneManager.GetApexZone(updatedMemberEntry.Key);
  391. if ((memberApexZone is not null) && _name.Equals(memberApexZone.CatalogZoneName, StringComparison.OrdinalIgnoreCase))
  392. {
  393. //change of ownership property
  394. {
  395. string newCatalogZoneName = GetChangeOfOwnershipProperty(updatedMemberEntry.Value);
  396. if (newCatalogZoneName is not null)
  397. {
  398. ApexZone catalogApexZone = _dnsServer.AuthZoneManager.GetApexZone(newCatalogZoneName);
  399. if (catalogApexZone is SecondaryCatalogZone secondaryCatalogZone)
  400. {
  401. //found secondary catalog zone; transfer ownership to it
  402. memberApexZone.CatalogZoneName = secondaryCatalogZone._name;
  403. }
  404. else
  405. {
  406. //no such secondary catalog zone exists; delete member zone
  407. DeleteMemberZone(memberApexZone);
  408. continue;
  409. }
  410. }
  411. }
  412. //allow query member zone custom property
  413. {
  414. IReadOnlyCollection<NetworkAccessControl> allowQueryACL = GetAllowQueryProperty(updatedMemberEntry.Value);
  415. if (allowQueryACL.Count > 0)
  416. {
  417. memberApexZone.QueryAccess = GetQueryAccessType(allowQueryACL);
  418. switch (memberApexZone.QueryAccess)
  419. {
  420. case AuthZoneQueryAccess.UseSpecifiedNetworkACL:
  421. memberApexZone.QueryAccessNetworkACL = allowQueryACL;
  422. break;
  423. case AuthZoneQueryAccess.AllowZoneNameServersAndUseSpecifiedNetworkACL:
  424. memberApexZone.QueryAccessNetworkACL = GetFilteredACL(allowQueryACL);
  425. break;
  426. default:
  427. memberApexZone.QueryAccessNetworkACL = null;
  428. break;
  429. }
  430. memberApexZone.OverrideCatalogQueryAccess = true;
  431. }
  432. else
  433. {
  434. memberApexZone.OverrideCatalogQueryAccess = false;
  435. memberApexZone.QueryAccess = AuthZoneQueryAccess.Allow;
  436. memberApexZone.QueryAccessNetworkACL = null;
  437. }
  438. }
  439. if (memberApexZone is StubZone stubZone)
  440. {
  441. //primary addresses property
  442. stubZone.PrimaryNameServerAddresses = GetPrimaryAddressesProperty(updatedMemberEntry.Value);
  443. }
  444. else if (memberApexZone is SecondaryForwarderZone)
  445. {
  446. //do nothing
  447. }
  448. else if (memberApexZone is SecondaryZone secondaryZone)
  449. {
  450. //primaries property
  451. IReadOnlyList<Tuple<IPAddress, string>> primaries = GetPrimariesProperty(updatedMemberEntry.Value);
  452. if (primaries.Count == 0)
  453. primaries = GetPrimariesProperty(_name);
  454. if (primaries.Count > 0)
  455. {
  456. Tuple<IPAddress, string> primary = primaries[0];
  457. secondaryZone.PrimaryNameServerAddresses = [new NameServerAddress(primary.Item1, DnsTransportProtocol.Tcp)];
  458. secondaryZone.PrimaryZoneTransferProtocol = DnsTransportProtocol.Tcp;
  459. secondaryZone.PrimaryZoneTransferTsigKeyName = primary.Item2;
  460. secondaryZone.OverrideCatalogPrimaryNameServers = true;
  461. }
  462. else
  463. {
  464. secondaryZone.OverrideCatalogPrimaryNameServers = false;
  465. secondaryZone.PrimaryNameServerAddresses = null;
  466. secondaryZone.PrimaryZoneTransferProtocol = DnsTransportProtocol.Tcp;
  467. secondaryZone.PrimaryZoneTransferTsigKeyName = null;
  468. }
  469. //allow transfer member zone custom property
  470. IReadOnlyCollection<NetworkAccessControl> allowTransferACL = GetAllowTransferProperty(updatedMemberEntry.Value);
  471. if (allowTransferACL.Count > 0)
  472. {
  473. memberApexZone.ZoneTransfer = GetZoneTransferType(allowTransferACL);
  474. switch (memberApexZone.ZoneTransfer)
  475. {
  476. case AuthZoneTransfer.UseSpecifiedNetworkACL:
  477. memberApexZone.ZoneTransferNetworkACL = allowTransferACL;
  478. break;
  479. case AuthZoneTransfer.AllowZoneNameServersAndUseSpecifiedNetworkACL:
  480. memberApexZone.ZoneTransferNetworkACL = GetFilteredACL(allowTransferACL);
  481. break;
  482. default:
  483. memberApexZone.ZoneTransferNetworkACL = null;
  484. break;
  485. }
  486. //zone tranfer tsig key names member zone custom property
  487. memberApexZone.ZoneTransferTsigKeyNames = GetZoneTransferTsigKeyNamesProperty(updatedMemberEntry.Value);
  488. memberApexZone.OverrideCatalogZoneTransfer = true;
  489. }
  490. else
  491. {
  492. memberApexZone.OverrideCatalogZoneTransfer = false;
  493. memberApexZone.ZoneTransfer = AuthZoneTransfer.Deny;
  494. memberApexZone.ZoneTransferNetworkACL = null;
  495. memberApexZone.ZoneTransferTsigKeyNames = null;
  496. }
  497. }
  498. _dnsServer.AuthZoneManager.SaveZoneFile(memberApexZone.Name);
  499. }
  500. }
  501. }
  502. private void DeleteMemberZone(ApexZone apexZone)
  503. {
  504. AuthZoneInfo zoneInfo = new AuthZoneInfo(apexZone);
  505. if (_dnsServer.AuthZoneManager.DeleteZone(zoneInfo, true))
  506. {
  507. ZoneRemoved?.Invoke(this, new SecondaryCatalogEventArgs(zoneInfo));
  508. _dnsServer.LogManager?.Write(apexZone.GetZoneTypeName() + " zone '" + apexZone.ToString() + "' was removed via Secondary Catalog zone '" + ToString() + "' sucessfully.");
  509. }
  510. }
  511. private string GetVersion()
  512. {
  513. string domain = "version." + _name;
  514. IReadOnlyList<DnsResourceRecord> records = _dnsServer.AuthZoneManager.GetRecords(_name, domain, DnsResourceRecordType.TXT);
  515. if (records.Count > 0)
  516. return (records[0].RDATA as DnsTXTRecordData).GetText();
  517. return null;
  518. }
  519. private string GetChangeOfOwnershipProperty(string memberZoneDomain)
  520. {
  521. string domain = "coo." + memberZoneDomain;
  522. IReadOnlyList<DnsResourceRecord> records = _dnsServer.AuthZoneManager.GetRecords(_name, domain, DnsResourceRecordType.PTR);
  523. if (records.Count > 0)
  524. return (records[0].RDATA as DnsPTRRecordData).Domain;
  525. return null;
  526. }
  527. private AuthZoneType GetZoneTypeProperty(string memberZoneDomain)
  528. {
  529. string domain = "zone-type.ext." + memberZoneDomain;
  530. IReadOnlyList<DnsResourceRecord> records = _dnsServer.AuthZoneManager.GetRecords(_name, domain, DnsResourceRecordType.TXT);
  531. if (records.Count > 0)
  532. return Enum.Parse<AuthZoneType>((records[0].RDATA as DnsTXTRecordData).GetText(), true);
  533. return AuthZoneType.Primary;
  534. }
  535. private List<Tuple<IPAddress, string>> GetPrimariesProperty(string memberZoneDomain)
  536. {
  537. string domain = "primaries.ext." + memberZoneDomain;
  538. List<Tuple<IPAddress, string>> primaries = new List<Tuple<IPAddress, string>>(2);
  539. AuthZone authZone = _dnsServer.AuthZoneManager.GetAuthZone(_name, domain);
  540. if (authZone is not null)
  541. {
  542. foreach (DnsResourceRecord record in authZone.GetRecords(DnsResourceRecordType.A))
  543. primaries.Add(new Tuple<IPAddress, string>((record.RDATA as DnsARecordData).Address, null));
  544. foreach (DnsResourceRecord record in authZone.GetRecords(DnsResourceRecordType.AAAA))
  545. primaries.Add(new Tuple<IPAddress, string>((record.RDATA as DnsAAAARecordData).Address, null));
  546. }
  547. List<string> subdomains = new List<string>();
  548. _dnsServer.AuthZoneManager.ListSubDomains(domain, subdomains);
  549. foreach (string subdomain in subdomains)
  550. {
  551. AuthZone subZone = _dnsServer.AuthZoneManager.GetAuthZone(_name, subdomain + "." + domain);
  552. if (subZone is null)
  553. continue;
  554. string tsigKeyName = null;
  555. IReadOnlyList<DnsResourceRecord> szTXTRecords = subZone.GetRecords(DnsResourceRecordType.TXT);
  556. if (szTXTRecords.Count > 0)
  557. tsigKeyName = (szTXTRecords[0].RDATA as DnsTXTRecordData).GetText();
  558. foreach (DnsResourceRecord record in subZone.GetRecords(DnsResourceRecordType.A))
  559. primaries.Add(new Tuple<IPAddress, string>((record.RDATA as DnsARecordData).Address, tsigKeyName));
  560. foreach (DnsResourceRecord record in subZone.GetRecords(DnsResourceRecordType.AAAA))
  561. primaries.Add(new Tuple<IPAddress, string>((record.RDATA as DnsAAAARecordData).Address, tsigKeyName));
  562. }
  563. return primaries;
  564. }
  565. private IReadOnlyList<NameServerAddress> GetPrimaryAddressesProperty(string memberZoneDomain)
  566. {
  567. string domain = "primary-addresses.ext." + memberZoneDomain;
  568. IReadOnlyList<DnsResourceRecord> records = _dnsServer.AuthZoneManager.GetRecords(_name, domain, DnsResourceRecordType.TXT);
  569. if (records.Count > 0)
  570. return (records[0].RDATA as DnsTXTRecordData).CharacterStrings.Convert(NameServerAddress.Parse);
  571. return [];
  572. }
  573. private IReadOnlyCollection<NetworkAccessControl> GetAllowQueryProperty(string memberZoneDomain)
  574. {
  575. string domain = "allow-query.ext." + memberZoneDomain;
  576. IReadOnlyList<DnsResourceRecord> records = _dnsServer.AuthZoneManager.GetRecords(_name, domain, DnsResourceRecordType.APL);
  577. if (records.Count > 0)
  578. return NetworkAccessControl.ConvertFromAPLRecordData(records[0].RDATA as DnsAPLRecordData);
  579. return [];
  580. }
  581. private IReadOnlyCollection<NetworkAccessControl> GetAllowTransferProperty(string memberZoneDomain)
  582. {
  583. string domain = "allow-transfer.ext." + memberZoneDomain;
  584. IReadOnlyList<DnsResourceRecord> records = _dnsServer.AuthZoneManager.GetRecords(_name, domain, DnsResourceRecordType.APL);
  585. if (records.Count > 0)
  586. return NetworkAccessControl.ConvertFromAPLRecordData(records[0].RDATA as DnsAPLRecordData);
  587. return [];
  588. }
  589. private Dictionary<string, object> GetZoneTransferTsigKeyNamesProperty(string memberZoneDomain)
  590. {
  591. string domain = "transfer-tsig-key-names.ext." + memberZoneDomain;
  592. IReadOnlyList<DnsResourceRecord> records = _dnsServer.AuthZoneManager.GetRecords(_name, domain, DnsResourceRecordType.PTR);
  593. Dictionary<string, object> keyNames = new Dictionary<string, object>(records.Count);
  594. foreach (DnsResourceRecord record in records)
  595. keyNames.TryAdd((record.RDATA as DnsPTRRecordData).Domain.ToLowerInvariant(), null);
  596. return keyNames;
  597. }
  598. private static AuthZoneQueryAccess GetQueryAccessType(IReadOnlyCollection<NetworkAccessControl> acl)
  599. {
  600. if (acl.HasSameItems(_allowACL))
  601. return AuthZoneQueryAccess.Allow;
  602. if (acl.HasSameItems(_queryAccessAllowOnlyPrivateNetworksACL))
  603. return AuthZoneQueryAccess.AllowOnlyPrivateNetworks;
  604. if (acl.HasSameItems(_allowOnlyZoneNameServersACL))
  605. return AuthZoneQueryAccess.AllowOnlyZoneNameServers;
  606. if ((acl.Count > 1) && acl.Contains(_allowZoneNameServersAndUseSpecifiedNetworkACL))
  607. return AuthZoneQueryAccess.AllowZoneNameServersAndUseSpecifiedNetworkACL;
  608. if (acl.HasSameItems(_denyACL))
  609. return AuthZoneQueryAccess.Deny;
  610. return AuthZoneQueryAccess.UseSpecifiedNetworkACL;
  611. }
  612. private static AuthZoneTransfer GetZoneTransferType(IReadOnlyCollection<NetworkAccessControl> acl)
  613. {
  614. if (acl.HasSameItems(_allowACL))
  615. return AuthZoneTransfer.Allow;
  616. if (acl.HasSameItems(_allowOnlyZoneNameServersACL))
  617. return AuthZoneTransfer.AllowOnlyZoneNameServers;
  618. if ((acl.Count > 1) && acl.Contains(_allowZoneNameServersAndUseSpecifiedNetworkACL))
  619. return AuthZoneTransfer.AllowZoneNameServersAndUseSpecifiedNetworkACL;
  620. if (acl.HasSameItems(_denyACL))
  621. return AuthZoneTransfer.Deny;
  622. return AuthZoneTransfer.UseSpecifiedNetworkACL;
  623. }
  624. private static List<NetworkAccessControl> GetFilteredACL(IReadOnlyCollection<NetworkAccessControl> acl)
  625. {
  626. List<NetworkAccessControl> filteredACL = new List<NetworkAccessControl>(acl.Count);
  627. foreach (NetworkAccessControl ac in acl)
  628. {
  629. if (ac.Equals(_allowZoneNameServersAndUseSpecifiedNetworkACL))
  630. continue;
  631. filteredACL.Add(ac);
  632. }
  633. return filteredACL;
  634. }
  635. #endregion
  636. #region public
  637. public override string GetZoneTypeName()
  638. {
  639. return "Secondary Catalog";
  640. }
  641. public override IReadOnlyList<DnsResourceRecord> QueryRecords(DnsResourceRecordType type, bool dnssecOk)
  642. {
  643. return []; //secondary catalog zone is not queriable
  644. }
  645. #endregion
  646. #region properties
  647. public override string CatalogZoneName
  648. {
  649. get { return base.CatalogZoneName; }
  650. set { throw new InvalidOperationException(); }
  651. }
  652. public override bool OverrideCatalogQueryAccess
  653. {
  654. get { throw new InvalidOperationException(); }
  655. set { throw new InvalidOperationException(); }
  656. }
  657. public override AuthZoneQueryAccess QueryAccess
  658. {
  659. get { return _queryAccess; }
  660. set { throw new InvalidOperationException(); }
  661. }
  662. public override AuthZoneTransfer ZoneTransfer
  663. {
  664. get { return _zoneTransfer; }
  665. set { throw new InvalidOperationException(); }
  666. }
  667. public override AuthZoneUpdate Update
  668. {
  669. get { return base.Update; }
  670. set { throw new InvalidOperationException(); }
  671. }
  672. #endregion
  673. }
  674. public class SecondaryCatalogEventArgs : EventArgs
  675. {
  676. #region variables
  677. readonly AuthZoneInfo _zoneInfo;
  678. #endregion
  679. #region constructor
  680. public SecondaryCatalogEventArgs(AuthZoneInfo zoneInfo)
  681. {
  682. _zoneInfo = zoneInfo;
  683. }
  684. #endregion
  685. #region properties
  686. public AuthZoneInfo ZoneInfo
  687. { get { return _zoneInfo; } }
  688. #endregion
  689. }
  690. }