AuthZone.cs 36 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932
  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. throw new InvalidOperationException();
  392. List<DnsResourceRecordType> typesToRefresh = new List<DnsResourceRecordType>();
  393. DateTime utcNow = DateTime.UtcNow;
  394. foreach (DnsResourceRecord rrsigRecord in rrsigRecords)
  395. {
  396. DnsRRSIGRecordData rrsig = rrsigRecord.RDATA as DnsRRSIGRecordData;
  397. uint signatureValidityPeriod = rrsig.SignatureExpiration - rrsig.SignatureInception;
  398. uint refreshPeriod = signatureValidityPeriod / 3;
  399. if (utcNow > DateTime.UnixEpoch.AddSeconds(rrsig.SignatureExpiration - refreshPeriod))
  400. typesToRefresh.Add(rrsig.TypeCovered);
  401. }
  402. List<DnsResourceRecord> newRRSigRecords = new List<DnsResourceRecord>(typesToRefresh.Count);
  403. foreach (DnsResourceRecordType type in typesToRefresh)
  404. {
  405. if (_entries.TryGetValue(type, out IReadOnlyList<DnsResourceRecord> records))
  406. newRRSigRecords.AddRange(SignRRSet(records));
  407. }
  408. return newRRSigRecords;
  409. }
  410. internal virtual IReadOnlyList<DnsResourceRecord> SignRRSet(IReadOnlyList<DnsResourceRecord> records)
  411. {
  412. throw new InvalidOperationException();
  413. }
  414. internal IReadOnlyList<DnsResourceRecord> GetUpdatedNSecRRSet(string nextDomainName, uint ttl)
  415. {
  416. List<DnsResourceRecordType> types = new List<DnsResourceRecordType>(_entries.Count);
  417. foreach (KeyValuePair<DnsResourceRecordType, IReadOnlyList<DnsResourceRecord>> entry in _entries)
  418. types.Add(entry.Key);
  419. if (!_entries.ContainsKey(DnsResourceRecordType.NSEC))
  420. types.Add(DnsResourceRecordType.NSEC);
  421. if (!_entries.ContainsKey(DnsResourceRecordType.RRSIG))
  422. types.Add(DnsResourceRecordType.RRSIG);
  423. types.Sort();
  424. DnsNSECRecordData newNSecRecord = new DnsNSECRecordData(nextDomainName, types);
  425. if (!_entries.TryGetValue(DnsResourceRecordType.NSEC, out IReadOnlyList<DnsResourceRecord> existingRecords) || (existingRecords[0].TTL != ttl) || !existingRecords[0].RDATA.Equals(newNSecRecord))
  426. return new DnsResourceRecord[] { new DnsResourceRecord(_name, DnsResourceRecordType.NSEC, DnsClass.IN, ttl, newNSecRecord) };
  427. return Array.Empty<DnsResourceRecord>();
  428. }
  429. internal IReadOnlyList<DnsResourceRecord> GetUpdatedNSec3RRSet(IReadOnlyList<DnsResourceRecord> newNSec3Records)
  430. {
  431. if (!_entries.TryGetValue(DnsResourceRecordType.NSEC3, out IReadOnlyList<DnsResourceRecord> existingRecords) || (existingRecords[0].TTL != newNSec3Records[0].TTL) || !existingRecords[0].RDATA.Equals(newNSec3Records[0].RDATA))
  432. return newNSec3Records;
  433. return Array.Empty<DnsResourceRecord>();
  434. }
  435. internal IReadOnlyList<DnsResourceRecord> CreateNSec3RRSet(string hashedOwnerName, byte[] nextHashedOwnerName, uint ttl, ushort iterations, byte[] salt)
  436. {
  437. List<DnsResourceRecordType> types = new List<DnsResourceRecordType>(_entries.Count);
  438. foreach (KeyValuePair<DnsResourceRecordType, IReadOnlyList<DnsResourceRecord>> entry in _entries)
  439. {
  440. switch (entry.Key)
  441. {
  442. case DnsResourceRecordType.NSEC3:
  443. case DnsResourceRecordType.RRSIG:
  444. continue;
  445. default:
  446. types.Add(entry.Key);
  447. break;
  448. }
  449. }
  450. if (types.Count > 0)
  451. {
  452. //zone is not an empty non-terminal (ENT)
  453. if (!_entries.ContainsKey(DnsResourceRecordType.RRSIG))
  454. types.Add(DnsResourceRecordType.RRSIG);
  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. case DnsResourceRecordType.RRSIG:
  469. continue;
  470. default:
  471. types.Add(entry.Key);
  472. break;
  473. }
  474. }
  475. if (_name.Equals(zoneName, StringComparison.OrdinalIgnoreCase))
  476. {
  477. types.Add(DnsResourceRecordType.NSEC3PARAM); //add NSEC3PARAM type to NSEC3 for unsigned zone apex
  478. if (!_entries.ContainsKey(DnsResourceRecordType.RRSIG))
  479. types.Add(DnsResourceRecordType.RRSIG);
  480. }
  481. else if (types.Count > 0)
  482. {
  483. //zone is not an empty non-terminal (ENT)
  484. if (!_entries.ContainsKey(DnsResourceRecordType.RRSIG))
  485. types.Add(DnsResourceRecordType.RRSIG);
  486. }
  487. types.Sort();
  488. DnsNSEC3RecordData newNSec3Record = new DnsNSEC3RecordData(DnssecNSEC3HashAlgorithm.SHA1, DnssecNSEC3Flags.None, iterations, salt, Array.Empty<byte>(), types);
  489. return new DnsResourceRecord(newNSec3Record.ComputeHashedOwnerName(_name) + (zoneName.Length > 0 ? "." + zoneName : ""), DnsResourceRecordType.NSEC3, DnsClass.IN, ttl, newNSec3Record);
  490. }
  491. #endregion
  492. #region public
  493. public void SyncRecords(Dictionary<DnsResourceRecordType, List<DnsResourceRecord>> newEntries)
  494. {
  495. //remove entires of type that do not exists in new entries
  496. foreach (KeyValuePair<DnsResourceRecordType, IReadOnlyList<DnsResourceRecord>> entry in _entries)
  497. {
  498. if (!newEntries.ContainsKey(entry.Key))
  499. _entries.TryRemove(entry.Key, out _);
  500. }
  501. //set new entries into zone
  502. if (this is ForwarderZone)
  503. {
  504. //skip NS and SOA records from being added to ForwarderZone
  505. foreach (KeyValuePair<DnsResourceRecordType, List<DnsResourceRecord>> newEntry in newEntries)
  506. {
  507. switch (newEntry.Key)
  508. {
  509. case DnsResourceRecordType.NS:
  510. case DnsResourceRecordType.SOA:
  511. break;
  512. default:
  513. _entries[newEntry.Key] = newEntry.Value;
  514. break;
  515. }
  516. }
  517. }
  518. else
  519. {
  520. foreach (KeyValuePair<DnsResourceRecordType, List<DnsResourceRecord>> newEntry in newEntries)
  521. {
  522. if (newEntry.Key == DnsResourceRecordType.SOA)
  523. {
  524. if (newEntry.Value.Count != 1)
  525. continue; //skip invalid SOA record
  526. if (this is SecondaryZone)
  527. {
  528. //copy existing SOA record's info to new SOA record
  529. DnsResourceRecord existingSoaRecord = _entries[DnsResourceRecordType.SOA][0];
  530. DnsResourceRecord newSoaRecord = newEntry.Value[0];
  531. newSoaRecord.CopyRecordInfoFrom(existingSoaRecord);
  532. }
  533. }
  534. _entries[newEntry.Key] = newEntry.Value;
  535. }
  536. }
  537. }
  538. public void SyncRecords(Dictionary<DnsResourceRecordType, List<DnsResourceRecord>> deletedEntries, Dictionary<DnsResourceRecordType, List<DnsResourceRecord>> addedEntries)
  539. {
  540. if (deletedEntries is not null)
  541. {
  542. foreach (KeyValuePair<DnsResourceRecordType, List<DnsResourceRecord>> deletedEntry in deletedEntries)
  543. {
  544. if (_entries.TryGetValue(deletedEntry.Key, out IReadOnlyList<DnsResourceRecord> existingRecords))
  545. {
  546. List<DnsResourceRecord> updatedRecords = new List<DnsResourceRecord>(Math.Max(0, existingRecords.Count - deletedEntry.Value.Count));
  547. foreach (DnsResourceRecord existingRecord in existingRecords)
  548. {
  549. bool deleted = false;
  550. foreach (DnsResourceRecord deletedRecord in deletedEntry.Value)
  551. {
  552. if (existingRecord.RDATA.Equals(deletedRecord.RDATA))
  553. {
  554. deleted = true;
  555. break;
  556. }
  557. }
  558. if (!deleted)
  559. updatedRecords.Add(existingRecord);
  560. }
  561. if (existingRecords.Count > updatedRecords.Count)
  562. {
  563. if (updatedRecords.Count > 0)
  564. _entries[deletedEntry.Key] = updatedRecords;
  565. else
  566. _entries.TryRemove(deletedEntry.Key, out _);
  567. }
  568. }
  569. }
  570. }
  571. if (addedEntries is not null)
  572. {
  573. foreach (KeyValuePair<DnsResourceRecordType, List<DnsResourceRecord>> addedEntry in addedEntries)
  574. {
  575. _entries.AddOrUpdate(addedEntry.Key, addedEntry.Value, delegate (DnsResourceRecordType key, IReadOnlyList<DnsResourceRecord> existingRecords)
  576. {
  577. List<DnsResourceRecord> updatedRecords = new List<DnsResourceRecord>(existingRecords.Count + addedEntry.Value.Count);
  578. updatedRecords.AddRange(existingRecords);
  579. foreach (DnsResourceRecord addedRecord in addedEntry.Value)
  580. {
  581. bool exists = false;
  582. foreach (DnsResourceRecord existingRecord in existingRecords)
  583. {
  584. if (addedRecord.RDATA.Equals(existingRecord.RDATA))
  585. {
  586. exists = true;
  587. break;
  588. }
  589. }
  590. if (!exists)
  591. updatedRecords.Add(addedRecord);
  592. }
  593. if (updatedRecords.Count > existingRecords.Count)
  594. return updatedRecords;
  595. else
  596. return existingRecords;
  597. });
  598. }
  599. }
  600. }
  601. public void SyncGlueRecords(IReadOnlyCollection<DnsResourceRecord> deletedGlueRecords, IReadOnlyCollection<DnsResourceRecord> addedGlueRecords)
  602. {
  603. if (_entries.TryGetValue(DnsResourceRecordType.NS, out IReadOnlyList<DnsResourceRecord> nsRecords))
  604. {
  605. foreach (DnsResourceRecord nsRecord in nsRecords)
  606. nsRecord.SyncGlueRecords(deletedGlueRecords, addedGlueRecords);
  607. }
  608. }
  609. public void LoadRecords(DnsResourceRecordType type, IReadOnlyList<DnsResourceRecord> records)
  610. {
  611. _entries[type] = records;
  612. }
  613. public virtual void SetRecords(DnsResourceRecordType type, IReadOnlyList<DnsResourceRecord> records)
  614. {
  615. _entries[type] = records;
  616. }
  617. public virtual void AddRecord(DnsResourceRecord record)
  618. {
  619. AddRecord(record, out _, out _);
  620. }
  621. public virtual bool DeleteRecords(DnsResourceRecordType type)
  622. {
  623. return _entries.TryRemove(type, out _);
  624. }
  625. public virtual bool DeleteRecord(DnsResourceRecordType type, DnsResourceRecordData rdata)
  626. {
  627. return TryDeleteRecord(type, rdata, out _);
  628. }
  629. public virtual void UpdateRecord(DnsResourceRecord oldRecord, DnsResourceRecord newRecord)
  630. {
  631. if (oldRecord.Type == DnsResourceRecordType.SOA)
  632. throw new InvalidOperationException("Cannot update record: use SetRecords() for " + oldRecord.Type.ToString() + " record");
  633. if (oldRecord.Type != newRecord.Type)
  634. throw new InvalidOperationException("Old and new record types do not match.");
  635. if (!DeleteRecord(oldRecord.Type, oldRecord.RDATA))
  636. throw new DnsWebServiceException("Cannot update record: the old record does not exists.");
  637. AddRecord(newRecord);
  638. }
  639. public virtual IReadOnlyList<DnsResourceRecord> QueryRecords(DnsResourceRecordType type, bool dnssecOk)
  640. {
  641. switch (type)
  642. {
  643. case DnsResourceRecordType.APP:
  644. case DnsResourceRecordType.FWD:
  645. case DnsResourceRecordType.NSEC:
  646. case DnsResourceRecordType.NSEC3:
  647. {
  648. //return only exact type if exists
  649. if (_entries.TryGetValue(type, out IReadOnlyList<DnsResourceRecord> existingRecords))
  650. {
  651. IReadOnlyList<DnsResourceRecord> filteredRecords = FilterDisabledRecords(type, existingRecords);
  652. if (filteredRecords.Count > 0)
  653. {
  654. if (dnssecOk)
  655. return AppendRRSigTo(filteredRecords);
  656. return filteredRecords;
  657. }
  658. }
  659. }
  660. break;
  661. case DnsResourceRecordType.ANY:
  662. List<DnsResourceRecord> records = new List<DnsResourceRecord>(_entries.Count * 2);
  663. foreach (KeyValuePair<DnsResourceRecordType, IReadOnlyList<DnsResourceRecord>> entry in _entries)
  664. {
  665. switch (entry.Key)
  666. {
  667. case DnsResourceRecordType.FWD:
  668. case DnsResourceRecordType.APP:
  669. //skip records
  670. continue;
  671. default:
  672. records.AddRange(entry.Value);
  673. break;
  674. }
  675. }
  676. return FilterDisabledRecords(type, records);
  677. default:
  678. {
  679. //check for CNAME
  680. if (_entries.TryGetValue(DnsResourceRecordType.CNAME, out IReadOnlyList<DnsResourceRecord> existingCNAMERecords))
  681. {
  682. IReadOnlyList<DnsResourceRecord> filteredRecords = FilterDisabledRecords(type, existingCNAMERecords);
  683. if (filteredRecords.Count > 0)
  684. {
  685. if (dnssecOk)
  686. return AppendRRSigTo(filteredRecords);
  687. return filteredRecords;
  688. }
  689. }
  690. //check for exact type
  691. if (_entries.TryGetValue(type, out IReadOnlyList<DnsResourceRecord> existingRecords))
  692. {
  693. IReadOnlyList<DnsResourceRecord> filteredRecords = FilterDisabledRecords(type, existingRecords);
  694. if (filteredRecords.Count > 0)
  695. {
  696. if (dnssecOk)
  697. return AppendRRSigTo(filteredRecords);
  698. return filteredRecords;
  699. }
  700. }
  701. //check special processing
  702. switch (type)
  703. {
  704. case DnsResourceRecordType.A:
  705. case DnsResourceRecordType.AAAA:
  706. //check for ANAME
  707. if (_entries.TryGetValue(DnsResourceRecordType.ANAME, out IReadOnlyList<DnsResourceRecord> anameRecords))
  708. return FilterDisabledRecords(type, anameRecords);
  709. break;
  710. }
  711. }
  712. break;
  713. }
  714. return Array.Empty<DnsResourceRecord>();
  715. }
  716. public IReadOnlyList<DnsResourceRecord> GetRecords(DnsResourceRecordType type)
  717. {
  718. if (_entries.TryGetValue(type, out IReadOnlyList<DnsResourceRecord> records))
  719. return records;
  720. return Array.Empty<DnsResourceRecord>();
  721. }
  722. public IReadOnlyDictionary<DnsResourceRecordType, IReadOnlyList<DnsResourceRecord>> GetAllRecords()
  723. {
  724. return _entries;
  725. }
  726. public override bool ContainsNameServerRecords()
  727. {
  728. if (!_entries.TryGetValue(DnsResourceRecordType.NS, out IReadOnlyList<DnsResourceRecord> records))
  729. return false;
  730. foreach (DnsResourceRecord record in records)
  731. {
  732. if (record.GetAuthRecordInfo().Disabled)
  733. continue;
  734. return true;
  735. }
  736. return false;
  737. }
  738. #endregion
  739. #region properties
  740. public virtual bool Disabled
  741. {
  742. get { return _disabled; }
  743. set { _disabled = value; }
  744. }
  745. public virtual bool IsActive
  746. {
  747. get { return !_disabled; }
  748. }
  749. #endregion
  750. }
  751. }