AuthZone.cs 36 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924
  1. /*
  2. Technitium DNS Server
  3. Copyright (C) 2023 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 TechnitiumLibrary;
  19. using TechnitiumLibrary.Net.Dns.ResourceRecords;
  20. namespace DnsServerCore.Dns.Zones
  21. {
  22. abstract class AuthZone : Zone
  23. {
  24. #region variables
  25. protected bool _disabled;
  26. #endregion
  27. #region constructor
  28. protected AuthZone(AuthZoneInfo zoneInfo)
  29. : base(zoneInfo.Name)
  30. {
  31. _disabled = zoneInfo.Disabled;
  32. }
  33. protected AuthZone(string name)
  34. : base(name)
  35. { }
  36. #endregion
  37. #region private
  38. private IReadOnlyList<DnsResourceRecord> FilterDisabledRecords(DnsResourceRecordType type, IReadOnlyList<DnsResourceRecord> records)
  39. {
  40. if (_disabled)
  41. return Array.Empty<DnsResourceRecord>();
  42. if (records.Count == 1)
  43. {
  44. AuthRecordInfo authRecordInfo = records[0].GetAuthRecordInfo();
  45. if (authRecordInfo.Disabled)
  46. return Array.Empty<DnsResourceRecord>(); //record disabled
  47. //update last used on
  48. authRecordInfo.LastUsedOn = DateTime.UtcNow;
  49. return records;
  50. }
  51. List<DnsResourceRecord> newRecords = new List<DnsResourceRecord>(records.Count);
  52. DateTime utcNow = DateTime.UtcNow;
  53. foreach (DnsResourceRecord record in records)
  54. {
  55. AuthRecordInfo authRecordInfo = record.GetAuthRecordInfo();
  56. if (authRecordInfo.Disabled)
  57. continue; //record disabled
  58. //update last used on
  59. authRecordInfo.LastUsedOn = utcNow;
  60. newRecords.Add(record);
  61. }
  62. if (newRecords.Count > 1)
  63. {
  64. switch (type)
  65. {
  66. case DnsResourceRecordType.A:
  67. case DnsResourceRecordType.AAAA:
  68. case DnsResourceRecordType.NS:
  69. newRecords.Shuffle(); //shuffle records to allow load balancing
  70. break;
  71. }
  72. }
  73. return newRecords;
  74. }
  75. private IReadOnlyList<DnsResourceRecord> AppendRRSigTo(IReadOnlyList<DnsResourceRecord> records)
  76. {
  77. IReadOnlyList<DnsResourceRecord> rrsigRecords = GetRecords(DnsResourceRecordType.RRSIG);
  78. if (rrsigRecords.Count == 0)
  79. return records;
  80. DnsResourceRecordType type = records[0].Type;
  81. List<DnsResourceRecord> newRecords = new List<DnsResourceRecord>(records.Count + 2);
  82. newRecords.AddRange(records);
  83. DateTime utcNow = DateTime.UtcNow;
  84. foreach (DnsResourceRecord rrsigRecord in rrsigRecords)
  85. {
  86. if ((rrsigRecord.RDATA as DnsRRSIGRecordData).TypeCovered == type)
  87. {
  88. rrsigRecord.GetAuthRecordInfo().LastUsedOn = utcNow;
  89. newRecords.Add(rrsigRecord);
  90. }
  91. }
  92. return newRecords;
  93. }
  94. #endregion
  95. #region versioning
  96. internal bool TrySetRecords(DnsResourceRecordType type, IReadOnlyList<DnsResourceRecord> records, out IReadOnlyList<DnsResourceRecord> deletedRecords)
  97. {
  98. if (_entries.TryGetValue(type, out IReadOnlyList<DnsResourceRecord> existingRecords))
  99. {
  100. deletedRecords = existingRecords;
  101. return _entries.TryUpdate(type, records, existingRecords);
  102. }
  103. else
  104. {
  105. deletedRecords = Array.Empty<DnsResourceRecord>();
  106. return _entries.TryAdd(type, records);
  107. }
  108. }
  109. internal bool TryDeleteRecord(DnsResourceRecordType type, DnsResourceRecordData rdata, out DnsResourceRecord deletedRecord)
  110. {
  111. if (_entries.TryGetValue(type, out IReadOnlyList<DnsResourceRecord> existingRecords))
  112. {
  113. if (existingRecords.Count == 1)
  114. {
  115. if (rdata.Equals(existingRecords[0].RDATA))
  116. {
  117. if (_entries.TryRemove(type, out IReadOnlyList<DnsResourceRecord> removedRecords))
  118. {
  119. deletedRecord = removedRecords[0];
  120. return true;
  121. }
  122. }
  123. }
  124. else
  125. {
  126. deletedRecord = null;
  127. List<DnsResourceRecord> updatedRecords = new List<DnsResourceRecord>(existingRecords.Count);
  128. foreach (DnsResourceRecord existingRecord in existingRecords)
  129. {
  130. if ((deletedRecord is null) && rdata.Equals(existingRecord.RDATA))
  131. deletedRecord = existingRecord;
  132. else
  133. updatedRecords.Add(existingRecord);
  134. }
  135. if (deletedRecord is null)
  136. return false; //not found
  137. return _entries.TryUpdate(type, updatedRecords, existingRecords);
  138. }
  139. }
  140. deletedRecord = null;
  141. return false;
  142. }
  143. internal bool TryDeleteRecords(DnsResourceRecordType type, IReadOnlyList<DnsResourceRecord> records, out IReadOnlyList<DnsResourceRecord> deletedRecords)
  144. {
  145. if (_entries.TryGetValue(type, out IReadOnlyList<DnsResourceRecord> existingRecords))
  146. {
  147. if (existingRecords.Count == 1)
  148. {
  149. DnsResourceRecord existingRecord = existingRecords[0];
  150. foreach (DnsResourceRecord record in records)
  151. {
  152. if (record.RDATA.Equals(existingRecord.RDATA))
  153. {
  154. if (_entries.TryRemove(type, out IReadOnlyList<DnsResourceRecord> removedRecords))
  155. {
  156. deletedRecords = removedRecords;
  157. return true;
  158. }
  159. }
  160. }
  161. }
  162. else
  163. {
  164. List<DnsResourceRecord> deleted = new List<DnsResourceRecord>(records.Count);
  165. List<DnsResourceRecord> updatedRecords = new List<DnsResourceRecord>(existingRecords.Count);
  166. foreach (DnsResourceRecord existingRecord in existingRecords)
  167. {
  168. bool found = false;
  169. foreach (DnsResourceRecord record in records)
  170. {
  171. if (record.RDATA.Equals(existingRecord.RDATA))
  172. {
  173. found = true;
  174. break;
  175. }
  176. }
  177. if (found)
  178. deleted.Add(existingRecord);
  179. else
  180. updatedRecords.Add(existingRecord);
  181. }
  182. if (deleted.Count > 0)
  183. {
  184. deletedRecords = deleted;
  185. if (updatedRecords.Count > 0)
  186. return _entries.TryUpdate(type, updatedRecords, existingRecords);
  187. return _entries.TryRemove(type, out _);
  188. }
  189. }
  190. }
  191. deletedRecords = null;
  192. return false;
  193. }
  194. internal void AddOrUpdateRRSigRecords(IReadOnlyList<DnsResourceRecord> newRRSigRecords, out IReadOnlyList<DnsResourceRecord> deletedRRSigRecords)
  195. {
  196. IReadOnlyList<DnsResourceRecord> deleted = null;
  197. _entries.AddOrUpdate(DnsResourceRecordType.RRSIG, delegate (DnsResourceRecordType key)
  198. {
  199. deleted = Array.Empty<DnsResourceRecord>();
  200. return newRRSigRecords;
  201. },
  202. delegate (DnsResourceRecordType key, IReadOnlyList<DnsResourceRecord> existingRecords)
  203. {
  204. List<DnsResourceRecord> updatedRecords = new List<DnsResourceRecord>(existingRecords.Count + newRRSigRecords.Count);
  205. List<DnsResourceRecord> deletedRecords = new List<DnsResourceRecord>();
  206. foreach (DnsResourceRecord existingRecord in existingRecords)
  207. {
  208. bool found = false;
  209. DnsRRSIGRecordData existingRRSig = existingRecord.RDATA as DnsRRSIGRecordData;
  210. foreach (DnsResourceRecord newRRSigRecord in newRRSigRecords)
  211. {
  212. DnsRRSIGRecordData newRRSig = newRRSigRecord.RDATA as DnsRRSIGRecordData;
  213. if ((newRRSig.TypeCovered == existingRRSig.TypeCovered) && (newRRSig.KeyTag == existingRRSig.KeyTag))
  214. {
  215. deletedRecords.Add(existingRecord);
  216. found = true;
  217. break;
  218. }
  219. }
  220. if (!found)
  221. updatedRecords.Add(existingRecord);
  222. }
  223. updatedRecords.AddRange(newRRSigRecords);
  224. deleted = deletedRecords;
  225. return updatedRecords;
  226. });
  227. deletedRRSigRecords = deleted;
  228. }
  229. internal void AddRecord(DnsResourceRecord record, out IReadOnlyList<DnsResourceRecord> addedRecords, out IReadOnlyList<DnsResourceRecord> deletedRecords)
  230. {
  231. switch (record.Type)
  232. {
  233. case DnsResourceRecordType.CNAME:
  234. case DnsResourceRecordType.DNAME:
  235. case DnsResourceRecordType.SOA:
  236. throw new InvalidOperationException("Cannot add record: use SetRecords() for " + record.Type.ToString() + " record");
  237. }
  238. List<DnsResourceRecord> added = new List<DnsResourceRecord>();
  239. List<DnsResourceRecord> deleted = new List<DnsResourceRecord>();
  240. addedRecords = added;
  241. deletedRecords = deleted;
  242. _entries.AddOrUpdate(record.Type, delegate (DnsResourceRecordType key)
  243. {
  244. added.Add(record);
  245. return new DnsResourceRecord[] { record };
  246. },
  247. delegate (DnsResourceRecordType key, IReadOnlyList<DnsResourceRecord> existingRecords)
  248. {
  249. foreach (DnsResourceRecord existingRecord in existingRecords)
  250. {
  251. if (record.RDATA.Equals(existingRecord.RDATA))
  252. return existingRecords;
  253. }
  254. List<DnsResourceRecord> updatedRecords = new List<DnsResourceRecord>(existingRecords.Count + 1);
  255. foreach (DnsResourceRecord existingRecord in existingRecords)
  256. {
  257. if (existingRecord.OriginalTtlValue == record.OriginalTtlValue)
  258. {
  259. updatedRecords.Add(existingRecord);
  260. }
  261. else
  262. {
  263. DnsResourceRecord updatedExistingRecord = new DnsResourceRecord(existingRecord.Name, existingRecord.Type, existingRecord.Class, record.OriginalTtlValue, existingRecord.RDATA);
  264. updatedRecords.Add(updatedExistingRecord);
  265. added.Add(updatedExistingRecord);
  266. deleted.Add(existingRecord);
  267. }
  268. }
  269. updatedRecords.Add(record);
  270. added.Add(record);
  271. return updatedRecords;
  272. });
  273. }
  274. #endregion
  275. #region DNSSEC
  276. internal IReadOnlyList<DnsResourceRecord> SignAllRRSets()
  277. {
  278. List<DnsResourceRecord> rrsigRecords = new List<DnsResourceRecord>(_entries.Count);
  279. foreach (KeyValuePair<DnsResourceRecordType, IReadOnlyList<DnsResourceRecord>> entry in _entries)
  280. {
  281. if (entry.Key == DnsResourceRecordType.RRSIG)
  282. continue;
  283. rrsigRecords.AddRange(SignRRSet(entry.Value));
  284. }
  285. return rrsigRecords;
  286. }
  287. internal IReadOnlyList<DnsResourceRecord> RemoveAllDnssecRecords()
  288. {
  289. List<DnsResourceRecord> allRemovedRecords = new List<DnsResourceRecord>();
  290. foreach (KeyValuePair<DnsResourceRecordType, IReadOnlyList<DnsResourceRecord>> entry in _entries)
  291. {
  292. switch (entry.Key)
  293. {
  294. case DnsResourceRecordType.DNSKEY:
  295. case DnsResourceRecordType.RRSIG:
  296. case DnsResourceRecordType.NSEC:
  297. case DnsResourceRecordType.NSEC3PARAM:
  298. case DnsResourceRecordType.NSEC3:
  299. if (_entries.TryRemove(entry.Key, out IReadOnlyList<DnsResourceRecord> removedRecords))
  300. allRemovedRecords.AddRange(removedRecords);
  301. break;
  302. }
  303. }
  304. return allRemovedRecords;
  305. }
  306. internal IReadOnlyList<DnsResourceRecord> RemoveNSecRecordsWithRRSig()
  307. {
  308. List<DnsResourceRecord> allRemovedRecords = new List<DnsResourceRecord>(2);
  309. foreach (KeyValuePair<DnsResourceRecordType, IReadOnlyList<DnsResourceRecord>> entry in _entries)
  310. {
  311. switch (entry.Key)
  312. {
  313. case DnsResourceRecordType.NSEC:
  314. if (_entries.TryRemove(entry.Key, out IReadOnlyList<DnsResourceRecord> removedRecords))
  315. allRemovedRecords.AddRange(removedRecords);
  316. break;
  317. case DnsResourceRecordType.RRSIG:
  318. List<DnsResourceRecord> recordsToRemove = new List<DnsResourceRecord>(1);
  319. foreach (DnsResourceRecord rrsigRecord in entry.Value)
  320. {
  321. DnsRRSIGRecordData rrsig = rrsigRecord.RDATA as DnsRRSIGRecordData;
  322. if (rrsig.TypeCovered == DnsResourceRecordType.NSEC)
  323. recordsToRemove.Add(rrsigRecord);
  324. }
  325. if (recordsToRemove.Count > 0)
  326. {
  327. if (TryDeleteRecords(DnsResourceRecordType.RRSIG, recordsToRemove, out IReadOnlyList<DnsResourceRecord> deletedRecords))
  328. allRemovedRecords.AddRange(deletedRecords);
  329. }
  330. break;
  331. }
  332. }
  333. return allRemovedRecords;
  334. }
  335. internal IReadOnlyList<DnsResourceRecord> RemoveNSec3RecordsWithRRSig()
  336. {
  337. List<DnsResourceRecord> allRemovedRecords = new List<DnsResourceRecord>(2);
  338. foreach (KeyValuePair<DnsResourceRecordType, IReadOnlyList<DnsResourceRecord>> entry in _entries)
  339. {
  340. switch (entry.Key)
  341. {
  342. case DnsResourceRecordType.NSEC3:
  343. case DnsResourceRecordType.NSEC3PARAM:
  344. if (_entries.TryRemove(entry.Key, out IReadOnlyList<DnsResourceRecord> removedRecords))
  345. allRemovedRecords.AddRange(removedRecords);
  346. break;
  347. case DnsResourceRecordType.RRSIG:
  348. List<DnsResourceRecord> recordsToRemove = new List<DnsResourceRecord>(1);
  349. foreach (DnsResourceRecord rrsigRecord in entry.Value)
  350. {
  351. DnsRRSIGRecordData rrsig = rrsigRecord.RDATA as DnsRRSIGRecordData;
  352. switch (rrsig.TypeCovered)
  353. {
  354. case DnsResourceRecordType.NSEC3:
  355. case DnsResourceRecordType.NSEC3PARAM:
  356. recordsToRemove.Add(rrsigRecord);
  357. break;
  358. }
  359. }
  360. if (recordsToRemove.Count > 0)
  361. {
  362. if (TryDeleteRecords(DnsResourceRecordType.RRSIG, recordsToRemove, out IReadOnlyList<DnsResourceRecord> deletedRecords))
  363. allRemovedRecords.AddRange(deletedRecords);
  364. }
  365. break;
  366. }
  367. }
  368. return allRemovedRecords;
  369. }
  370. internal bool HasOnlyNSec3Records()
  371. {
  372. if (!_entries.ContainsKey(DnsResourceRecordType.NSEC3))
  373. return false;
  374. foreach (KeyValuePair<DnsResourceRecordType, IReadOnlyList<DnsResourceRecord>> entry in _entries)
  375. {
  376. switch (entry.Key)
  377. {
  378. case DnsResourceRecordType.NSEC3:
  379. case DnsResourceRecordType.RRSIG:
  380. break;
  381. default:
  382. //found non NSEC3 records
  383. return false;
  384. }
  385. }
  386. return true;
  387. }
  388. internal IReadOnlyList<DnsResourceRecord> RefreshSignatures()
  389. {
  390. if (!_entries.TryGetValue(DnsResourceRecordType.RRSIG, out IReadOnlyList<DnsResourceRecord> rrsigRecords))
  391. {
  392. if ((_entries.Count == 1) && _entries.TryGetValue(DnsResourceRecordType.NS, out _))
  393. return Array.Empty<DnsResourceRecord>(); //delegation NS records are not signed
  394. throw new InvalidOperationException();
  395. }
  396. List<DnsResourceRecordType> typesToRefresh = new List<DnsResourceRecordType>();
  397. DateTime utcNow = DateTime.UtcNow;
  398. foreach (DnsResourceRecord rrsigRecord in rrsigRecords)
  399. {
  400. DnsRRSIGRecordData rrsig = rrsigRecord.RDATA as DnsRRSIGRecordData;
  401. uint signatureValidityPeriod = rrsig.SignatureExpiration - rrsig.SignatureInception;
  402. uint refreshPeriod = signatureValidityPeriod / 3;
  403. if (utcNow > DateTime.UnixEpoch.AddSeconds(rrsig.SignatureExpiration - refreshPeriod))
  404. typesToRefresh.Add(rrsig.TypeCovered);
  405. }
  406. List<DnsResourceRecord> newRRSigRecords = new List<DnsResourceRecord>(typesToRefresh.Count);
  407. foreach (DnsResourceRecordType type in typesToRefresh)
  408. {
  409. if (_entries.TryGetValue(type, out IReadOnlyList<DnsResourceRecord> records))
  410. newRRSigRecords.AddRange(SignRRSet(records));
  411. }
  412. return newRRSigRecords;
  413. }
  414. internal virtual IReadOnlyList<DnsResourceRecord> SignRRSet(IReadOnlyList<DnsResourceRecord> records)
  415. {
  416. throw new NotImplementedException();
  417. }
  418. internal IReadOnlyList<DnsResourceRecord> GetUpdatedNSecRRSet(string nextDomainName, uint ttl)
  419. {
  420. List<DnsResourceRecordType> types = new List<DnsResourceRecordType>(_entries.Count);
  421. foreach (KeyValuePair<DnsResourceRecordType, IReadOnlyList<DnsResourceRecord>> entry in _entries)
  422. types.Add(entry.Key);
  423. if (!types.Contains(DnsResourceRecordType.NSEC))
  424. {
  425. types.Add(DnsResourceRecordType.NSEC);
  426. if (!types.Contains(DnsResourceRecordType.RRSIG))
  427. types.Add(DnsResourceRecordType.RRSIG);
  428. }
  429. types.Sort();
  430. DnsNSECRecordData newNSecRecord = new DnsNSECRecordData(nextDomainName, types);
  431. if (!_entries.TryGetValue(DnsResourceRecordType.NSEC, out IReadOnlyList<DnsResourceRecord> existingRecords) || (existingRecords[0].TTL != ttl) || !existingRecords[0].RDATA.Equals(newNSecRecord))
  432. return new DnsResourceRecord[] { new DnsResourceRecord(_name, DnsResourceRecordType.NSEC, DnsClass.IN, ttl, newNSecRecord) };
  433. return Array.Empty<DnsResourceRecord>();
  434. }
  435. internal IReadOnlyList<DnsResourceRecord> GetUpdatedNSec3RRSet(IReadOnlyList<DnsResourceRecord> newNSec3Records)
  436. {
  437. if (!_entries.TryGetValue(DnsResourceRecordType.NSEC3, out IReadOnlyList<DnsResourceRecord> existingRecords) || (existingRecords[0].TTL != newNSec3Records[0].TTL) || !existingRecords[0].RDATA.Equals(newNSec3Records[0].RDATA))
  438. return newNSec3Records;
  439. return Array.Empty<DnsResourceRecord>();
  440. }
  441. internal IReadOnlyList<DnsResourceRecord> CreateNSec3RRSet(string hashedOwnerName, byte[] nextHashedOwnerName, uint ttl, ushort iterations, byte[] salt)
  442. {
  443. List<DnsResourceRecordType> types = new List<DnsResourceRecordType>(_entries.Count);
  444. foreach (KeyValuePair<DnsResourceRecordType, IReadOnlyList<DnsResourceRecord>> entry in _entries)
  445. {
  446. switch (entry.Key)
  447. {
  448. case DnsResourceRecordType.NSEC3:
  449. //rare case when there is a record created at the same name as that of an existing NSEC3
  450. continue;
  451. default:
  452. types.Add(entry.Key);
  453. break;
  454. }
  455. }
  456. types.Sort();
  457. DnsNSEC3RecordData newNSec3 = new DnsNSEC3RecordData(DnssecNSEC3HashAlgorithm.SHA1, DnssecNSEC3Flags.None, iterations, salt, nextHashedOwnerName, types);
  458. return new DnsResourceRecord[] { new DnsResourceRecord(hashedOwnerName, DnsResourceRecordType.NSEC3, DnsClass.IN, ttl, newNSec3) };
  459. }
  460. internal DnsResourceRecord GetPartialNSec3Record(string zoneName, uint ttl, ushort iterations, byte[] salt)
  461. {
  462. List<DnsResourceRecordType> types = new List<DnsResourceRecordType>(_entries.Count);
  463. foreach (KeyValuePair<DnsResourceRecordType, IReadOnlyList<DnsResourceRecord>> entry in _entries)
  464. {
  465. switch (entry.Key)
  466. {
  467. case DnsResourceRecordType.NSEC3:
  468. //rare case when there is a record created at the same name as that of an existing NSEC3
  469. continue;
  470. default:
  471. types.Add(entry.Key);
  472. break;
  473. }
  474. }
  475. if (_name.Equals(zoneName, StringComparison.OrdinalIgnoreCase))
  476. {
  477. if (!types.Contains(DnsResourceRecordType.NSEC3PARAM))
  478. types.Add(DnsResourceRecordType.NSEC3PARAM); //add NSEC3PARAM type to NSEC3 for unsigned zone apex
  479. }
  480. types.Sort();
  481. DnsNSEC3RecordData newNSec3Record = new DnsNSEC3RecordData(DnssecNSEC3HashAlgorithm.SHA1, DnssecNSEC3Flags.None, iterations, salt, Array.Empty<byte>(), types);
  482. return new DnsResourceRecord(newNSec3Record.ComputeHashedOwnerName(_name) + (zoneName.Length > 0 ? "." + zoneName : ""), DnsResourceRecordType.NSEC3, DnsClass.IN, ttl, newNSec3Record);
  483. }
  484. #endregion
  485. #region public
  486. public void SyncRecords(Dictionary<DnsResourceRecordType, List<DnsResourceRecord>> newEntries)
  487. {
  488. //remove entires of type that do not exists in new entries
  489. foreach (KeyValuePair<DnsResourceRecordType, IReadOnlyList<DnsResourceRecord>> entry in _entries)
  490. {
  491. if (!newEntries.ContainsKey(entry.Key))
  492. _entries.TryRemove(entry.Key, out _);
  493. }
  494. //set new entries into zone
  495. if (this is ForwarderZone)
  496. {
  497. //skip NS and SOA records from being added to ForwarderZone
  498. foreach (KeyValuePair<DnsResourceRecordType, List<DnsResourceRecord>> newEntry in newEntries)
  499. {
  500. switch (newEntry.Key)
  501. {
  502. case DnsResourceRecordType.NS:
  503. case DnsResourceRecordType.SOA:
  504. break;
  505. default:
  506. _entries[newEntry.Key] = newEntry.Value;
  507. break;
  508. }
  509. }
  510. }
  511. else
  512. {
  513. foreach (KeyValuePair<DnsResourceRecordType, List<DnsResourceRecord>> newEntry in newEntries)
  514. {
  515. if (newEntry.Key == DnsResourceRecordType.SOA)
  516. {
  517. if (newEntry.Value.Count != 1)
  518. continue; //skip invalid SOA record
  519. if (this is SecondaryZone)
  520. {
  521. //copy existing SOA record's info to new SOA record
  522. DnsResourceRecord existingSoaRecord = _entries[DnsResourceRecordType.SOA][0];
  523. DnsResourceRecord newSoaRecord = newEntry.Value[0];
  524. newSoaRecord.CopyRecordInfoFrom(existingSoaRecord);
  525. }
  526. }
  527. _entries[newEntry.Key] = newEntry.Value;
  528. }
  529. }
  530. }
  531. public void SyncRecords(Dictionary<DnsResourceRecordType, List<DnsResourceRecord>> deletedEntries, Dictionary<DnsResourceRecordType, List<DnsResourceRecord>> addedEntries)
  532. {
  533. if (deletedEntries is not null)
  534. {
  535. foreach (KeyValuePair<DnsResourceRecordType, List<DnsResourceRecord>> deletedEntry in deletedEntries)
  536. {
  537. if (_entries.TryGetValue(deletedEntry.Key, out IReadOnlyList<DnsResourceRecord> existingRecords))
  538. {
  539. List<DnsResourceRecord> updatedRecords = new List<DnsResourceRecord>(Math.Max(0, existingRecords.Count - deletedEntry.Value.Count));
  540. foreach (DnsResourceRecord existingRecord in existingRecords)
  541. {
  542. bool deleted = false;
  543. foreach (DnsResourceRecord deletedRecord in deletedEntry.Value)
  544. {
  545. if (existingRecord.RDATA.Equals(deletedRecord.RDATA))
  546. {
  547. deleted = true;
  548. break;
  549. }
  550. }
  551. if (!deleted)
  552. updatedRecords.Add(existingRecord);
  553. }
  554. if (existingRecords.Count > updatedRecords.Count)
  555. {
  556. if (updatedRecords.Count > 0)
  557. _entries[deletedEntry.Key] = updatedRecords;
  558. else
  559. _entries.TryRemove(deletedEntry.Key, out _);
  560. }
  561. }
  562. }
  563. }
  564. if (addedEntries is not null)
  565. {
  566. foreach (KeyValuePair<DnsResourceRecordType, List<DnsResourceRecord>> addedEntry in addedEntries)
  567. {
  568. _entries.AddOrUpdate(addedEntry.Key, addedEntry.Value, delegate (DnsResourceRecordType key, IReadOnlyList<DnsResourceRecord> existingRecords)
  569. {
  570. List<DnsResourceRecord> updatedRecords = new List<DnsResourceRecord>(existingRecords.Count + addedEntry.Value.Count);
  571. updatedRecords.AddRange(existingRecords);
  572. foreach (DnsResourceRecord addedRecord in addedEntry.Value)
  573. {
  574. bool exists = false;
  575. foreach (DnsResourceRecord existingRecord in existingRecords)
  576. {
  577. if (addedRecord.RDATA.Equals(existingRecord.RDATA))
  578. {
  579. exists = true;
  580. break;
  581. }
  582. }
  583. if (!exists)
  584. updatedRecords.Add(addedRecord);
  585. }
  586. if (updatedRecords.Count > existingRecords.Count)
  587. return updatedRecords;
  588. else
  589. return existingRecords;
  590. });
  591. }
  592. }
  593. }
  594. public void SyncGlueRecords(IReadOnlyCollection<DnsResourceRecord> deletedGlueRecords, IReadOnlyCollection<DnsResourceRecord> addedGlueRecords)
  595. {
  596. if (_entries.TryGetValue(DnsResourceRecordType.NS, out IReadOnlyList<DnsResourceRecord> nsRecords))
  597. {
  598. foreach (DnsResourceRecord nsRecord in nsRecords)
  599. nsRecord.SyncGlueRecords(deletedGlueRecords, addedGlueRecords);
  600. }
  601. }
  602. public void LoadRecords(DnsResourceRecordType type, IReadOnlyList<DnsResourceRecord> records)
  603. {
  604. _entries[type] = records;
  605. }
  606. public virtual void SetRecords(DnsResourceRecordType type, IReadOnlyList<DnsResourceRecord> records)
  607. {
  608. _entries[type] = records;
  609. }
  610. public virtual void AddRecord(DnsResourceRecord record)
  611. {
  612. AddRecord(record, out _, out _);
  613. }
  614. public virtual bool DeleteRecords(DnsResourceRecordType type)
  615. {
  616. return _entries.TryRemove(type, out _);
  617. }
  618. public virtual bool DeleteRecord(DnsResourceRecordType type, DnsResourceRecordData rdata)
  619. {
  620. return TryDeleteRecord(type, rdata, out _);
  621. }
  622. public virtual void UpdateRecord(DnsResourceRecord oldRecord, DnsResourceRecord newRecord)
  623. {
  624. if (oldRecord.Type == DnsResourceRecordType.SOA)
  625. throw new InvalidOperationException("Cannot update record: use SetRecords() for " + oldRecord.Type.ToString() + " record");
  626. if (oldRecord.Type != newRecord.Type)
  627. throw new InvalidOperationException("Old and new record types do not match.");
  628. if (!DeleteRecord(oldRecord.Type, oldRecord.RDATA))
  629. throw new DnsWebServiceException("Cannot update record: the old record does not exists.");
  630. AddRecord(newRecord);
  631. }
  632. public virtual IReadOnlyList<DnsResourceRecord> QueryRecords(DnsResourceRecordType type, bool dnssecOk)
  633. {
  634. switch (type)
  635. {
  636. case DnsResourceRecordType.APP:
  637. case DnsResourceRecordType.FWD:
  638. case DnsResourceRecordType.NSEC:
  639. case DnsResourceRecordType.NSEC3:
  640. {
  641. //return only exact type if exists
  642. if (_entries.TryGetValue(type, out IReadOnlyList<DnsResourceRecord> existingRecords))
  643. {
  644. IReadOnlyList<DnsResourceRecord> filteredRecords = FilterDisabledRecords(type, existingRecords);
  645. if (filteredRecords.Count > 0)
  646. {
  647. if (dnssecOk)
  648. return AppendRRSigTo(filteredRecords);
  649. return filteredRecords;
  650. }
  651. }
  652. }
  653. break;
  654. case DnsResourceRecordType.ANY:
  655. List<DnsResourceRecord> records = new List<DnsResourceRecord>(_entries.Count * 2);
  656. foreach (KeyValuePair<DnsResourceRecordType, IReadOnlyList<DnsResourceRecord>> entry in _entries)
  657. {
  658. switch (entry.Key)
  659. {
  660. case DnsResourceRecordType.FWD:
  661. case DnsResourceRecordType.APP:
  662. //skip records
  663. continue;
  664. default:
  665. records.AddRange(entry.Value);
  666. break;
  667. }
  668. }
  669. return FilterDisabledRecords(type, records);
  670. default:
  671. {
  672. //check for CNAME
  673. if (_entries.TryGetValue(DnsResourceRecordType.CNAME, out IReadOnlyList<DnsResourceRecord> existingCNAMERecords))
  674. {
  675. IReadOnlyList<DnsResourceRecord> filteredRecords = FilterDisabledRecords(type, existingCNAMERecords);
  676. if (filteredRecords.Count > 0)
  677. {
  678. if (dnssecOk)
  679. return AppendRRSigTo(filteredRecords);
  680. return filteredRecords;
  681. }
  682. }
  683. //check for exact type
  684. if (_entries.TryGetValue(type, out IReadOnlyList<DnsResourceRecord> existingRecords))
  685. {
  686. IReadOnlyList<DnsResourceRecord> filteredRecords = FilterDisabledRecords(type, existingRecords);
  687. if (filteredRecords.Count > 0)
  688. {
  689. if (dnssecOk)
  690. return AppendRRSigTo(filteredRecords);
  691. return filteredRecords;
  692. }
  693. }
  694. //check special processing
  695. switch (type)
  696. {
  697. case DnsResourceRecordType.A:
  698. case DnsResourceRecordType.AAAA:
  699. //check for ANAME
  700. if (_entries.TryGetValue(DnsResourceRecordType.ANAME, out IReadOnlyList<DnsResourceRecord> anameRecords))
  701. return FilterDisabledRecords(type, anameRecords);
  702. break;
  703. }
  704. }
  705. break;
  706. }
  707. return Array.Empty<DnsResourceRecord>();
  708. }
  709. public IReadOnlyList<DnsResourceRecord> GetRecords(DnsResourceRecordType type)
  710. {
  711. if (_entries.TryGetValue(type, out IReadOnlyList<DnsResourceRecord> records))
  712. return records;
  713. return Array.Empty<DnsResourceRecord>();
  714. }
  715. public IReadOnlyDictionary<DnsResourceRecordType, IReadOnlyList<DnsResourceRecord>> GetAllRecords()
  716. {
  717. return _entries;
  718. }
  719. public override bool ContainsNameServerRecords()
  720. {
  721. if (!_entries.TryGetValue(DnsResourceRecordType.NS, out IReadOnlyList<DnsResourceRecord> records))
  722. return false;
  723. foreach (DnsResourceRecord record in records)
  724. {
  725. if (record.GetAuthRecordInfo().Disabled)
  726. continue;
  727. return true;
  728. }
  729. return false;
  730. }
  731. #endregion
  732. #region properties
  733. public virtual bool Disabled
  734. {
  735. get { return _disabled; }
  736. set { _disabled = value; }
  737. }
  738. public virtual bool IsActive
  739. {
  740. get { return !_disabled; }
  741. }
  742. #endregion
  743. }
  744. }