CatalogZone.cs 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421
  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.Security.Cryptography;
  20. using System.Threading;
  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 CatalogZone : ForwarderZone
  28. {
  29. #region variables
  30. readonly Dictionary<string, string> _membersIndex = new Dictionary<string, string>();
  31. readonly ReaderWriterLockSlim _membersIndexLock = new ReaderWriterLockSlim();
  32. #endregion
  33. #region constructor
  34. public CatalogZone(DnsServer dnsServer, AuthZoneInfo zoneInfo)
  35. : base(dnsServer, zoneInfo)
  36. { }
  37. public CatalogZone(DnsServer dnsServer, string name)
  38. : base(dnsServer, name)
  39. { }
  40. #endregion
  41. #region IDisposable
  42. protected override void Dispose(bool disposing)
  43. {
  44. try
  45. {
  46. _membersIndexLock.Dispose();
  47. }
  48. finally
  49. {
  50. base.Dispose(disposing);
  51. }
  52. }
  53. #endregion
  54. #region internal
  55. internal override void InitZone()
  56. {
  57. //init catalog zone with dummy SOA and NS records
  58. DnsSOARecordData soa = new DnsSOARecordData("invalid", "invalid", 1, 300, 60, 604800, 900);
  59. DnsResourceRecord soaRecord = new DnsResourceRecord(_name, DnsResourceRecordType.SOA, DnsClass.IN, 0, soa);
  60. soaRecord.GetAuthGenericRecordInfo().LastModified = DateTime.UtcNow;
  61. _entries[DnsResourceRecordType.SOA] = [soaRecord];
  62. _entries[DnsResourceRecordType.NS] = [new DnsResourceRecord(_name, DnsResourceRecordType.NS, DnsClass.IN, 0, new DnsNSRecordData("invalid"))];
  63. }
  64. internal void InitZoneProperties()
  65. {
  66. //set catalog zone version record
  67. _dnsServer.AuthZoneManager.SetRecord(_name, new DnsResourceRecord("version." + _name, DnsResourceRecordType.TXT, DnsClass.IN, 0, new DnsTXTRecordData("2")));
  68. //init catalog global properties
  69. QueryAccess = AuthZoneQueryAccess.Allow;
  70. ZoneTransfer = AuthZoneTransfer.Deny;
  71. }
  72. internal void BuildMembersIndex()
  73. {
  74. foreach (KeyValuePair<string, string> memberEntry in EnumerateCatalogMemberZones(_dnsServer))
  75. _membersIndex.TryAdd(memberEntry.Key.ToLowerInvariant(), memberEntry.Value);
  76. }
  77. #endregion
  78. #region catalog
  79. public void AddMemberZone(string memberZoneName, AuthZoneType zoneType)
  80. {
  81. memberZoneName = memberZoneName.ToLowerInvariant();
  82. _membersIndexLock.EnterWriteLock();
  83. try
  84. {
  85. if (_membersIndex.TryGetValue(memberZoneName, out _))
  86. {
  87. if (_membersIndex.Remove(memberZoneName, out string removedMemberZoneDomain))
  88. {
  89. foreach (DnsResourceRecord record in _dnsServer.AuthZoneManager.EnumerateAllRecords(_name, removedMemberZoneDomain, true))
  90. _dnsServer.AuthZoneManager.DeleteRecord(_name, record);
  91. }
  92. }
  93. string memberZoneDomain = GetDomainWithLabel("zones." + _name);
  94. DateTime utcNow = DateTime.UtcNow;
  95. DnsResourceRecord ptrRecord = new DnsResourceRecord(memberZoneDomain, DnsResourceRecordType.PTR, DnsClass.IN, 0, new DnsPTRRecordData(memberZoneName));
  96. ptrRecord.GetAuthGenericRecordInfo().LastModified = utcNow;
  97. DnsResourceRecord txtRecord = new DnsResourceRecord("zone-type.ext." + memberZoneDomain, DnsResourceRecordType.TXT, DnsClass.IN, 0, new DnsTXTRecordData(zoneType.ToString().ToLowerInvariant()));
  98. txtRecord.GetAuthGenericRecordInfo().LastModified = utcNow;
  99. _dnsServer.AuthZoneManager.AddRecord(_name, ptrRecord);
  100. _dnsServer.AuthZoneManager.AddRecord(_name, txtRecord);
  101. _membersIndex[memberZoneName] = memberZoneDomain;
  102. }
  103. finally
  104. {
  105. _membersIndexLock.ExitWriteLock();
  106. }
  107. }
  108. public bool RemoveMemberZone(string memberZoneName)
  109. {
  110. memberZoneName = memberZoneName.ToLowerInvariant();
  111. _membersIndexLock.EnterWriteLock();
  112. try
  113. {
  114. if (_membersIndex.Remove(memberZoneName, out string removedMemberZoneDomain))
  115. {
  116. foreach (DnsResourceRecord record in _dnsServer.AuthZoneManager.EnumerateAllRecords(_name, removedMemberZoneDomain, true))
  117. _dnsServer.AuthZoneManager.DeleteRecord(_name, record);
  118. return true;
  119. }
  120. return false;
  121. }
  122. finally
  123. {
  124. _membersIndexLock.ExitWriteLock();
  125. }
  126. }
  127. public void ChangeMemberZoneOwnership(string memberZoneName, string newCatalogZoneName)
  128. {
  129. string memberZoneDomain = GetMemberZoneDomain(memberZoneName);
  130. string domain = "coo." + memberZoneDomain;
  131. DateTime utcNow = DateTime.UtcNow;
  132. uint soaExpiry = GetZoneSoaExpire();
  133. //add COO record with expiry
  134. DnsResourceRecord cooRecord = new DnsResourceRecord(domain, DnsResourceRecordType.PTR, DnsClass.IN, 0, new DnsPTRRecordData(newCatalogZoneName));
  135. GenericRecordInfo cooRecordInfo = cooRecord.GetAuthGenericRecordInfo();
  136. cooRecordInfo.LastModified = utcNow;
  137. cooRecordInfo.ExpiryTtl = soaExpiry;
  138. _dnsServer.AuthZoneManager.SetRecord(_name, cooRecord);
  139. //set expiry for other member zone records
  140. foreach (DnsResourceRecord record in _dnsServer.AuthZoneManager.EnumerateAllRecords(_name, memberZoneDomain, true))
  141. {
  142. GenericRecordInfo recordInfo = record.GetAuthGenericRecordInfo();
  143. recordInfo.LastModified = utcNow;
  144. recordInfo.ExpiryTtl = soaExpiry;
  145. }
  146. }
  147. public IReadOnlyCollection<string> GetAllMemberZoneNames()
  148. {
  149. _membersIndexLock.EnterReadLock();
  150. try
  151. {
  152. return _membersIndex.Keys.ToArray();
  153. }
  154. finally
  155. {
  156. _membersIndexLock.ExitReadLock();
  157. }
  158. }
  159. public void SetAllowQueryProperty(IReadOnlyCollection<NetworkAccessControl> acl = null, string memberZoneName = null)
  160. {
  161. string domain = "allow-query.ext." + GetMemberZoneDomain(memberZoneName);
  162. if (acl is null)
  163. {
  164. _dnsServer.AuthZoneManager.DeleteRecords(_name, domain, DnsResourceRecordType.APL);
  165. }
  166. else
  167. {
  168. DnsResourceRecord record = new DnsResourceRecord(domain, DnsResourceRecordType.APL, DnsClass.IN, 0, NetworkAccessControl.ConvertToAPLRecordData(acl));
  169. record.GetAuthGenericRecordInfo().LastModified = DateTime.UtcNow;
  170. _dnsServer.AuthZoneManager.SetRecord(_name, record);
  171. }
  172. }
  173. public void SetAllowTransferProperty(IReadOnlyCollection<NetworkAccessControl> acl = null, string memberZoneName = null)
  174. {
  175. string domain = "allow-transfer.ext." + GetMemberZoneDomain(memberZoneName);
  176. if (acl is null)
  177. {
  178. _dnsServer.AuthZoneManager.DeleteRecords(_name, domain, DnsResourceRecordType.APL);
  179. }
  180. else
  181. {
  182. DnsResourceRecord record = new DnsResourceRecord(domain, DnsResourceRecordType.APL, DnsClass.IN, 0, NetworkAccessControl.ConvertToAPLRecordData(acl));
  183. record.GetAuthGenericRecordInfo().LastModified = DateTime.UtcNow;
  184. _dnsServer.AuthZoneManager.SetRecord(_name, record);
  185. }
  186. }
  187. public void SetZoneTransferTsigKeyNamesProperty(IReadOnlyDictionary<string, object> tsigKeyNames = null, string memberZoneName = null)
  188. {
  189. string domain = "transfer-tsig-key-names.ext." + GetMemberZoneDomain(memberZoneName);
  190. if (tsigKeyNames is null)
  191. {
  192. _dnsServer.AuthZoneManager.DeleteRecords(_name, domain, DnsResourceRecordType.PTR);
  193. }
  194. else
  195. {
  196. DnsResourceRecord[] records = new DnsResourceRecord[tsigKeyNames.Count];
  197. int i = 0;
  198. foreach (KeyValuePair<string, object> entry in tsigKeyNames)
  199. {
  200. DnsResourceRecord record = new DnsResourceRecord(domain, DnsResourceRecordType.PTR, DnsClass.IN, 0, new DnsPTRRecordData(entry.Key));
  201. record.GetAuthGenericRecordInfo().LastModified = DateTime.UtcNow;
  202. records[i++] = record;
  203. }
  204. _dnsServer.AuthZoneManager.SetRecords(_name, records);
  205. }
  206. }
  207. public void SetPrimaryAddressesProperty(IReadOnlyList<NameServerAddress> primaryServerAddresses = null, string memberZoneName = null)
  208. {
  209. string domain = "primary-addresses.ext." + GetMemberZoneDomain(memberZoneName);
  210. if (primaryServerAddresses is null)
  211. {
  212. _dnsServer.AuthZoneManager.DeleteRecords(_name, domain, DnsResourceRecordType.TXT);
  213. }
  214. else
  215. {
  216. IReadOnlyList<string> charStrings = primaryServerAddresses.Convert(delegate (NameServerAddress nameServer)
  217. {
  218. return nameServer.ToString();
  219. });
  220. DnsResourceRecord record = new DnsResourceRecord(domain, DnsResourceRecordType.TXT, DnsClass.IN, 0, new DnsTXTRecordData(charStrings));
  221. record.GetAuthGenericRecordInfo().LastModified = DateTime.UtcNow;
  222. _dnsServer.AuthZoneManager.SetRecord(_name, record);
  223. }
  224. }
  225. private string GetMemberZoneDomain(string memberZoneName = null)
  226. {
  227. if (memberZoneName is null)
  228. {
  229. return _name;
  230. }
  231. else
  232. {
  233. memberZoneName = memberZoneName.ToLowerInvariant();
  234. _membersIndexLock.EnterReadLock();
  235. try
  236. {
  237. if (!_membersIndex.TryGetValue(memberZoneName, out string memberZoneDomain))
  238. throw new DnsServerException("Failed to find '" + memberZoneName + "' member zone entry in '" + ToString() + "' Catalog zone: member zone does not exists.");
  239. return memberZoneDomain;
  240. }
  241. finally
  242. {
  243. _membersIndexLock.ExitReadLock();
  244. }
  245. }
  246. }
  247. private string GetDomainWithLabel(string domain)
  248. {
  249. Span<byte> buffer = stackalloc byte[8];
  250. int i = 0;
  251. do
  252. {
  253. RandomNumberGenerator.Fill(buffer);
  254. string label = Base32.ToBase32HexString(buffer, true).ToLowerInvariant();
  255. string domainWithLabel = label + "." + domain;
  256. if (_dnsServer.AuthZoneManager.NameExists(_name, domainWithLabel))
  257. continue;
  258. return domainWithLabel;
  259. }
  260. while (++i < 10);
  261. throw new DnsServerException("Failed to generate unique label for the given domain name '" + domain + "'. Please try again.");
  262. }
  263. #endregion
  264. #region public
  265. public override string GetZoneTypeName()
  266. {
  267. return "Catalog";
  268. }
  269. public override void SetRecords(DnsResourceRecordType type, IReadOnlyList<DnsResourceRecord> records)
  270. {
  271. switch (type)
  272. {
  273. case DnsResourceRecordType.SOA:
  274. if ((records.Count != 1) || !records[0].Name.Equals(_name, StringComparison.OrdinalIgnoreCase))
  275. throw new InvalidOperationException("Invalid SOA record.");
  276. DnsResourceRecord newSoaRecord = records[0];
  277. DnsSOARecordData newSoa = newSoaRecord.RDATA as DnsSOARecordData;
  278. //reset fixed record values
  279. DnsSOARecordData modifiedSoa = new DnsSOARecordData("invalid", "invalid", newSoa.Serial, newSoa.Refresh, newSoa.Retry, newSoa.Expire, newSoa.Minimum);
  280. DnsResourceRecord modifiedSoaRecord = new DnsResourceRecord(_name, DnsResourceRecordType.SOA, DnsClass.IN, 0, modifiedSoa) { Tag = newSoaRecord.Tag };
  281. base.SetRecords(type, [modifiedSoaRecord]);
  282. break;
  283. default:
  284. throw new InvalidOperationException("Cannot set records in Catalog zone.");
  285. }
  286. }
  287. public override void AddRecord(DnsResourceRecord record)
  288. {
  289. throw new InvalidOperationException("Cannot add record in Catalog zone.");
  290. }
  291. public override bool DeleteRecords(DnsResourceRecordType type)
  292. {
  293. throw new InvalidOperationException("Cannot delete record in Catalog zone.");
  294. }
  295. public override bool DeleteRecord(DnsResourceRecordType type, DnsResourceRecordData record)
  296. {
  297. throw new InvalidOperationException("Cannot delete records in Catalog zone.");
  298. }
  299. public override void UpdateRecord(DnsResourceRecord oldRecord, DnsResourceRecord newRecord)
  300. {
  301. throw new InvalidOperationException("Cannot update record in Catalog zone.");
  302. }
  303. public override IReadOnlyList<DnsResourceRecord> QueryRecords(DnsResourceRecordType type, bool dnssecOk)
  304. {
  305. return []; //catalog zone is not queriable
  306. }
  307. #endregion
  308. #region properties
  309. public override string CatalogZoneName
  310. {
  311. get { return base.CatalogZoneName; }
  312. set { throw new InvalidOperationException(); }
  313. }
  314. public override bool OverrideCatalogQueryAccess
  315. {
  316. get { return base.OverrideCatalogQueryAccess; }
  317. set { throw new InvalidOperationException(); }
  318. }
  319. public override bool OverrideCatalogZoneTransfer
  320. {
  321. get { return base.OverrideCatalogZoneTransfer; }
  322. set { throw new InvalidOperationException(); }
  323. }
  324. public override bool OverrideCatalogNotify
  325. {
  326. get { return base.OverrideCatalogNotify; }
  327. set { throw new InvalidOperationException(); }
  328. }
  329. public override AuthZoneUpdate Update
  330. {
  331. get { return base.Update; }
  332. set { throw new InvalidOperationException(); }
  333. }
  334. #endregion
  335. }
  336. }