AuthZoneManager.cs 113 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504150515061507150815091510151115121513151415151516151715181519152015211522152315241525152615271528152915301531153215331534153515361537153815391540154115421543154415451546154715481549155015511552155315541555155615571558155915601561156215631564156515661567156815691570157115721573157415751576157715781579158015811582158315841585158615871588158915901591159215931594159515961597159815991600160116021603160416051606160716081609161016111612161316141615161616171618161916201621162216231624162516261627162816291630163116321633163416351636163716381639164016411642164316441645164616471648164916501651165216531654165516561657165816591660166116621663166416651666166716681669167016711672167316741675167616771678167916801681168216831684168516861687168816891690169116921693169416951696169716981699170017011702170317041705170617071708170917101711171217131714171517161717171817191720172117221723172417251726172717281729173017311732173317341735173617371738173917401741174217431744174517461747174817491750175117521753175417551756175717581759176017611762176317641765176617671768176917701771177217731774177517761777177817791780178117821783178417851786178717881789179017911792179317941795179617971798179918001801180218031804180518061807180818091810181118121813181418151816181718181819182018211822182318241825182618271828182918301831183218331834183518361837183818391840184118421843184418451846184718481849185018511852185318541855185618571858185918601861186218631864186518661867186818691870187118721873187418751876187718781879188018811882188318841885188618871888188918901891189218931894189518961897189818991900190119021903190419051906190719081909191019111912191319141915191619171918191919201921192219231924192519261927192819291930193119321933193419351936193719381939194019411942194319441945194619471948194919501951195219531954195519561957195819591960196119621963196419651966196719681969197019711972197319741975197619771978197919801981198219831984198519861987198819891990199119921993199419951996199719981999200020012002200320042005200620072008200920102011201220132014201520162017201820192020202120222023202420252026202720282029203020312032203320342035203620372038203920402041204220432044204520462047204820492050205120522053205420552056205720582059206020612062206320642065206620672068206920702071207220732074207520762077207820792080208120822083208420852086208720882089209020912092209320942095209620972098209921002101210221032104210521062107210821092110211121122113211421152116211721182119212021212122212321242125212621272128212921302131213221332134213521362137213821392140214121422143214421452146214721482149215021512152215321542155215621572158215921602161216221632164216521662167216821692170217121722173217421752176217721782179218021812182218321842185218621872188218921902191219221932194219521962197219821992200220122022203220422052206220722082209221022112212221322142215221622172218221922202221222222232224222522262227222822292230223122322233223422352236223722382239224022412242224322442245224622472248224922502251225222532254225522562257225822592260226122622263226422652266226722682269227022712272227322742275227622772278227922802281228222832284228522862287228822892290229122922293229422952296229722982299230023012302230323042305230623072308230923102311231223132314231523162317231823192320232123222323232423252326232723282329233023312332233323342335233623372338233923402341234223432344234523462347234823492350235123522353235423552356235723582359236023612362236323642365236623672368236923702371237223732374237523762377237823792380238123822383238423852386238723882389239023912392239323942395239623972398239924002401240224032404240524062407240824092410241124122413241424152416241724182419242024212422242324242425242624272428242924302431243224332434243524362437243824392440244124422443244424452446244724482449245024512452245324542455245624572458245924602461246224632464246524662467246824692470247124722473247424752476247724782479248024812482248324842485248624872488248924902491249224932494249524962497249824992500250125022503250425052506250725082509251025112512251325142515251625172518251925202521252225232524252525262527252825292530253125322533253425352536253725382539254025412542254325442545254625472548254925502551255225532554255525562557255825592560256125622563256425652566256725682569257025712572257325742575257625772578257925802581258225832584258525862587258825892590259125922593259425952596259725982599260026012602260326042605260626072608260926102611261226132614261526162617261826192620262126222623262426252626262726282629263026312632263326342635263626372638263926402641264226432644264526462647264826492650265126522653265426552656265726582659266026612662266326642665266626672668266926702671267226732674267526762677267826792680268126822683268426852686268726882689269026912692269326942695269626972698269927002701270227032704270527062707270827092710271127122713271427152716271727182719272027212722272327242725272627272728272927302731273227332734273527362737273827392740274127422743274427452746274727482749275027512752275327542755275627572758275927602761276227632764276527662767276827692770277127722773277427752776277727782779278027812782278327842785278627872788
  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.Dnssec;
  16. using DnsServerCore.Dns.ResourceRecords;
  17. using DnsServerCore.Dns.Trees;
  18. using DnsServerCore.Dns.Zones;
  19. using System;
  20. using System.Collections.Generic;
  21. using System.IO;
  22. using System.Net;
  23. using System.Text;
  24. using System.Threading;
  25. using System.Threading.Tasks;
  26. using TechnitiumLibrary.Net;
  27. using TechnitiumLibrary.Net.Dns;
  28. using TechnitiumLibrary.Net.Dns.ResourceRecords;
  29. using TechnitiumLibrary.Net.Proxy;
  30. namespace DnsServerCore.Dns.ZoneManagers
  31. {
  32. public sealed class AuthZoneManager : IDisposable
  33. {
  34. #region variables
  35. readonly DnsServer _dnsServer;
  36. string _serverDomain;
  37. readonly AuthZoneTree _root = new AuthZoneTree();
  38. readonly List<AuthZoneInfo> _zoneIndex = new List<AuthZoneInfo>(10);
  39. readonly ReaderWriterLockSlim _zoneIndexLock = new ReaderWriterLockSlim();
  40. #endregion
  41. #region constructor
  42. public AuthZoneManager(DnsServer dnsServer)
  43. {
  44. _dnsServer = dnsServer;
  45. _serverDomain = _dnsServer.ServerDomain;
  46. }
  47. #endregion
  48. #region IDisposable
  49. bool _disposed;
  50. private void Dispose(bool disposing)
  51. {
  52. if (_disposed)
  53. return;
  54. if (disposing)
  55. {
  56. foreach (AuthZoneNode zoneNode in _root)
  57. zoneNode.Dispose();
  58. }
  59. _disposed = true;
  60. }
  61. public void Dispose()
  62. {
  63. Dispose(true);
  64. }
  65. #endregion
  66. #region private
  67. private void UpdateServerDomain(string serverDomain)
  68. {
  69. ThreadPool.QueueUserWorkItem(delegate (object state)
  70. {
  71. //update authoritative zone SOA and NS records
  72. try
  73. {
  74. IReadOnlyList<AuthZoneInfo> zones = GetAllZones();
  75. foreach (AuthZoneInfo zone in zones)
  76. {
  77. if (zone.Type != AuthZoneType.Primary)
  78. continue;
  79. DnsResourceRecord record = zone.GetApexRecords(DnsResourceRecordType.SOA)[0];
  80. DnsSOARecordData soa = record.RDATA as DnsSOARecordData;
  81. if (soa.PrimaryNameServer.Equals(_serverDomain, StringComparison.OrdinalIgnoreCase))
  82. {
  83. string responsiblePerson = soa.ResponsiblePerson;
  84. if (responsiblePerson.EndsWith(_serverDomain))
  85. responsiblePerson = responsiblePerson.Replace(_serverDomain, serverDomain);
  86. SetRecords(zone.Name, record.Name, record.Type, record.TTL, new DnsResourceRecordData[] { new DnsSOARecordData(serverDomain, responsiblePerson, soa.Serial, soa.Refresh, soa.Retry, soa.Expire, soa.Minimum) });
  87. //update NS records
  88. IReadOnlyList<DnsResourceRecord> nsResourceRecords = zone.GetApexRecords(DnsResourceRecordType.NS);
  89. foreach (DnsResourceRecord nsResourceRecord in nsResourceRecords)
  90. {
  91. if ((nsResourceRecord.RDATA as DnsNSRecordData).NameServer.Equals(_serverDomain, StringComparison.OrdinalIgnoreCase))
  92. {
  93. UpdateRecord(zone.Name, nsResourceRecord, new DnsResourceRecord(nsResourceRecord.Name, nsResourceRecord.Type, nsResourceRecord.Class, nsResourceRecord.TTL, new DnsNSRecordData(serverDomain)) { Tag = nsResourceRecord.Tag });
  94. break;
  95. }
  96. }
  97. if (zone.Internal)
  98. continue; //dont save internal zones to disk
  99. try
  100. {
  101. SaveZoneFile(zone.Name);
  102. }
  103. catch (Exception ex)
  104. {
  105. _dnsServer.LogManager?.Write(ex);
  106. }
  107. }
  108. }
  109. }
  110. catch (Exception ex)
  111. {
  112. _dnsServer.LogManager?.Write(ex);
  113. }
  114. //update server domain
  115. _serverDomain = serverDomain;
  116. });
  117. }
  118. private ApexZone CreateEmptyZone(AuthZoneInfo zoneInfo)
  119. {
  120. ApexZone zone;
  121. switch (zoneInfo.Type)
  122. {
  123. case AuthZoneType.Primary:
  124. zone = new PrimaryZone(_dnsServer, zoneInfo);
  125. break;
  126. case AuthZoneType.Secondary:
  127. zone = new SecondaryZone(_dnsServer, zoneInfo);
  128. break;
  129. case AuthZoneType.Stub:
  130. zone = new StubZone(_dnsServer, zoneInfo);
  131. break;
  132. case AuthZoneType.Forwarder:
  133. zone = new ForwarderZone(zoneInfo);
  134. break;
  135. default:
  136. throw new InvalidDataException("DNS zone type not supported.");
  137. }
  138. if (_root.TryAdd(zone))
  139. return zone;
  140. throw new DnsServerException("Zone already exists: " + zoneInfo.Name);
  141. }
  142. internal AuthZone GetOrAddSubDomainZone(string zoneName, string domain)
  143. {
  144. return _root.GetOrAddSubDomainZone(zoneName, domain, delegate ()
  145. {
  146. if (!_root.TryGet(zoneName, out ApexZone apexZone))
  147. throw new DnsServerException("Zone was not found for domain: " + domain);
  148. if (apexZone is PrimaryZone primaryZone)
  149. return new PrimarySubDomainZone(primaryZone, domain);
  150. else if (apexZone is SecondaryZone secondaryZone)
  151. return new SecondarySubDomainZone(secondaryZone, domain);
  152. else if (apexZone is ForwarderZone forwarderZone)
  153. return new ForwarderSubDomainZone(forwarderZone, domain);
  154. throw new DnsServerException("Zone cannot have sub domains.");
  155. });
  156. }
  157. internal IReadOnlyList<AuthZone> GetZoneWithSubDomainZones(string zoneName)
  158. {
  159. return _root.GetZoneWithSubDomainZones(zoneName);
  160. }
  161. internal AuthZone GetAuthZone(string zoneName, string domain)
  162. {
  163. return _root.GetAuthZone(zoneName, domain);
  164. }
  165. internal AuthZone FindPreviousSubDomainZone(string zoneName, string domain)
  166. {
  167. return _root.FindPreviousSubDomainZone(zoneName, domain);
  168. }
  169. internal AuthZone FindNextSubDomainZone(string zoneName, string domain)
  170. {
  171. return _root.FindNextSubDomainZone(zoneName, domain);
  172. }
  173. internal bool SubDomainExists(string zoneName, string domain)
  174. {
  175. return _root.SubDomainExists(zoneName, domain);
  176. }
  177. internal void RemoveSubDomainZone(string domain)
  178. {
  179. _root.TryRemove(domain, out SubDomainZone _);
  180. }
  181. internal static string GetParentZone(string domain)
  182. {
  183. int i = domain.IndexOf('.');
  184. if (i > -1)
  185. return domain.Substring(i + 1);
  186. //dont return root zone
  187. return null;
  188. }
  189. private static void ValidateZoneNameFor(string zoneName, string domain)
  190. {
  191. if (domain.Equals(zoneName, StringComparison.OrdinalIgnoreCase) || domain.EndsWith("." + zoneName, StringComparison.OrdinalIgnoreCase) || (zoneName.Length == 0))
  192. return;
  193. throw new DnsServerException("The domain name does not belong to the zone: " + domain);
  194. }
  195. private void ResolveCNAME(DnsQuestionRecord question, bool dnssecOk, DnsResourceRecord lastCNAME, List<DnsResourceRecord> answerRecords)
  196. {
  197. int queryCount = 0;
  198. do
  199. {
  200. string cnameDomain = (lastCNAME.RDATA as DnsCNAMERecordData).Domain;
  201. if (lastCNAME.Name.Equals(cnameDomain, StringComparison.OrdinalIgnoreCase))
  202. break; //loop detected
  203. if (!_root.TryGet(cnameDomain, out AuthZoneNode zoneNode))
  204. break;
  205. IReadOnlyList<DnsResourceRecord> records = zoneNode.QueryRecords(question.Type, dnssecOk);
  206. if (records.Count < 1)
  207. break;
  208. DnsResourceRecord lastRR = records[records.Count - 1];
  209. if (lastRR.Type != DnsResourceRecordType.CNAME)
  210. {
  211. answerRecords.AddRange(records);
  212. break;
  213. }
  214. foreach (DnsResourceRecord answerRecord in answerRecords)
  215. {
  216. if (answerRecord.Type != DnsResourceRecordType.CNAME)
  217. continue;
  218. if (answerRecord.RDATA.Equals(lastRR.RDATA))
  219. return; //loop detected
  220. }
  221. answerRecords.AddRange(records);
  222. lastCNAME = lastRR;
  223. }
  224. while (++queryCount < DnsServer.MAX_CNAME_HOPS);
  225. }
  226. private bool DoDNAMESubstitution(DnsQuestionRecord question, bool dnssecOk, IReadOnlyList<DnsResourceRecord> answer, out IReadOnlyList<DnsResourceRecord> newAnswer)
  227. {
  228. DnsResourceRecord dnameRR = answer[0];
  229. string result = (dnameRR.RDATA as DnsDNAMERecordData).Substitute(question.Name, dnameRR.Name);
  230. if (DnsClient.IsDomainNameValid(result))
  231. {
  232. DnsResourceRecord cnameRR = new DnsResourceRecord(question.Name, DnsResourceRecordType.CNAME, question.Class, dnameRR.TTL, new DnsCNAMERecordData(result));
  233. List<DnsResourceRecord> list = new List<DnsResourceRecord>(5);
  234. list.AddRange(answer);
  235. list.Add(cnameRR);
  236. ResolveCNAME(question, dnssecOk, cnameRR, list);
  237. newAnswer = list;
  238. return true;
  239. }
  240. else
  241. {
  242. newAnswer = answer;
  243. return false;
  244. }
  245. }
  246. private IReadOnlyList<DnsResourceRecord> GetAdditionalRecords(IReadOnlyList<DnsResourceRecord> refRecords, bool dnssecOk)
  247. {
  248. List<DnsResourceRecord> additionalRecords = new List<DnsResourceRecord>(refRecords.Count);
  249. foreach (DnsResourceRecord refRecord in refRecords)
  250. {
  251. switch (refRecord.Type)
  252. {
  253. case DnsResourceRecordType.NS:
  254. IReadOnlyList<DnsResourceRecord> glueRecords = refRecord.GetAuthRecordInfo().GlueRecords;
  255. if (glueRecords is not null)
  256. {
  257. additionalRecords.AddRange(glueRecords);
  258. }
  259. else
  260. {
  261. ResolveAdditionalRecords(refRecord, (refRecord.RDATA as DnsNSRecordData).NameServer, dnssecOk, additionalRecords);
  262. }
  263. break;
  264. case DnsResourceRecordType.MX:
  265. ResolveAdditionalRecords(refRecord, (refRecord.RDATA as DnsMXRecordData).Exchange, dnssecOk, additionalRecords);
  266. break;
  267. case DnsResourceRecordType.SRV:
  268. ResolveAdditionalRecords(refRecord, (refRecord.RDATA as DnsSRVRecordData).Target, dnssecOk, additionalRecords);
  269. break;
  270. case DnsResourceRecordType.SVCB:
  271. case DnsResourceRecordType.HTTPS:
  272. DnsSVCBRecordData svcb = refRecord.RDATA as DnsSVCBRecordData;
  273. string targetName = svcb.TargetName;
  274. if (svcb.SvcPriority == 0)
  275. {
  276. //For AliasMode SVCB RRs, a TargetName of "." indicates that the service is not available or does not exist [draft-ietf-dnsop-svcb-https-12]
  277. if ((targetName.Length == 0) || targetName.Equals(refRecord.Name, StringComparison.OrdinalIgnoreCase))
  278. break;
  279. }
  280. else
  281. {
  282. //For ServiceMode SVCB RRs, if TargetName has the value ".", then the owner name of this record MUST be used as the effective TargetName [draft-ietf-dnsop-svcb-https-12]
  283. if (targetName.Length == 0)
  284. targetName = refRecord.Name;
  285. }
  286. ResolveAdditionalRecords(refRecord, targetName, dnssecOk, additionalRecords);
  287. break;
  288. }
  289. }
  290. return additionalRecords;
  291. }
  292. private void ResolveAdditionalRecords(DnsResourceRecord refRecord, string domain, bool dnssecOk, List<DnsResourceRecord> additionalRecords)
  293. {
  294. int count = 0;
  295. while ((count++ < DnsServer.MAX_CNAME_HOPS) && _root.TryGet(domain, out AuthZoneNode zoneNode) && zoneNode.IsActive)
  296. {
  297. if (((refRecord.Type == DnsResourceRecordType.SVCB) || (refRecord.Type == DnsResourceRecordType.HTTPS)) && ((refRecord.RDATA as DnsSVCBRecordData).SvcPriority == 0))
  298. {
  299. //resolve SVCB/HTTPS for Alias mode refRecord
  300. IReadOnlyList<DnsResourceRecord> records = zoneNode.QueryRecords(refRecord.Type, dnssecOk);
  301. if ((records.Count > 0) && (records[0].Type == refRecord.Type) && (records[0].RDATA is DnsSVCBRecordData svcb))
  302. {
  303. additionalRecords.AddRange(records);
  304. string targetName = svcb.TargetName;
  305. if (svcb.SvcPriority == 0)
  306. {
  307. //Alias mode
  308. if ((targetName.Length == 0) || targetName.Equals(records[0].Name, StringComparison.OrdinalIgnoreCase))
  309. break; //For AliasMode SVCB RRs, a TargetName of "." indicates that the service is not available or does not exist [draft-ietf-dnsop-svcb-https-12]
  310. foreach (DnsResourceRecord additionalRecord in additionalRecords)
  311. {
  312. if (additionalRecord.Name.Equals(targetName, StringComparison.OrdinalIgnoreCase))
  313. return; //loop detected
  314. }
  315. //continue to resolve SVCB/HTTPS further
  316. domain = targetName;
  317. refRecord = records[0];
  318. continue;
  319. }
  320. else
  321. {
  322. //Service mode
  323. if (targetName.Length > 0)
  324. {
  325. //continue to resolve A/AAAA for target name
  326. domain = targetName;
  327. refRecord = records[0];
  328. continue;
  329. }
  330. //resolve A/AAAA below
  331. }
  332. }
  333. }
  334. bool hasA = false;
  335. bool hasAAAA = false;
  336. if ((refRecord.Type == DnsResourceRecordType.SRV) || (refRecord.Type == DnsResourceRecordType.SVCB) || (refRecord.Type == DnsResourceRecordType.HTTPS))
  337. {
  338. foreach (DnsResourceRecord additionalRecord in additionalRecords)
  339. {
  340. if (additionalRecord.Name.Equals(domain, StringComparison.OrdinalIgnoreCase))
  341. {
  342. switch (additionalRecord.Type)
  343. {
  344. case DnsResourceRecordType.A:
  345. hasA = true;
  346. break;
  347. case DnsResourceRecordType.AAAA:
  348. hasAAAA = true;
  349. break;
  350. }
  351. }
  352. if (hasA && hasAAAA)
  353. break;
  354. }
  355. }
  356. if (!hasA)
  357. {
  358. IReadOnlyList<DnsResourceRecord> records = zoneNode.QueryRecords(DnsResourceRecordType.A, dnssecOk);
  359. if ((records.Count > 0) && (records[0].Type == DnsResourceRecordType.A))
  360. additionalRecords.AddRange(records);
  361. }
  362. if (!hasAAAA)
  363. {
  364. IReadOnlyList<DnsResourceRecord> records = zoneNode.QueryRecords(DnsResourceRecordType.AAAA, dnssecOk);
  365. if ((records.Count > 0) && (records[0].Type == DnsResourceRecordType.AAAA))
  366. additionalRecords.AddRange(records);
  367. }
  368. break;
  369. }
  370. }
  371. private DnsDatagram GetReferralResponse(DnsDatagram request, bool dnssecOk, AuthZone delegationZone, ApexZone apexZone)
  372. {
  373. IReadOnlyList<DnsResourceRecord> authority;
  374. if (delegationZone is StubZone)
  375. {
  376. authority = delegationZone.GetRecords(DnsResourceRecordType.NS); //stub zone has no authority so cant query
  377. //update last used on
  378. DateTime utcNow = DateTime.UtcNow;
  379. foreach (DnsResourceRecord record in authority)
  380. record.GetAuthRecordInfo().LastUsedOn = utcNow;
  381. }
  382. else
  383. {
  384. authority = delegationZone.QueryRecords(DnsResourceRecordType.NS, false);
  385. if (dnssecOk)
  386. {
  387. IReadOnlyList<DnsResourceRecord> dsRecords = delegationZone.QueryRecords(DnsResourceRecordType.DS, true);
  388. if (dsRecords.Count > 0)
  389. {
  390. List<DnsResourceRecord> newAuthority = new List<DnsResourceRecord>(authority.Count + dsRecords.Count);
  391. newAuthority.AddRange(authority);
  392. newAuthority.AddRange(dsRecords);
  393. authority = newAuthority;
  394. }
  395. else
  396. {
  397. //add proof of non existence (NODATA) to prove DS record does not exists
  398. IReadOnlyList<DnsResourceRecord> nsecRecords;
  399. if (apexZone.DnssecStatus == AuthZoneDnssecStatus.SignedWithNSEC3)
  400. nsecRecords = _root.FindNSec3ProofOfNonExistenceNoData(delegationZone, apexZone);
  401. else
  402. nsecRecords = _root.FindNSecProofOfNonExistenceNoData(delegationZone);
  403. if (nsecRecords.Count > 0)
  404. {
  405. List<DnsResourceRecord> newAuthority = new List<DnsResourceRecord>(authority.Count + nsecRecords.Count);
  406. newAuthority.AddRange(authority);
  407. newAuthority.AddRange(nsecRecords);
  408. authority = newAuthority;
  409. }
  410. }
  411. }
  412. }
  413. IReadOnlyList<DnsResourceRecord> additional = GetAdditionalRecords(authority, dnssecOk);
  414. return new DnsDatagram(request.Identifier, true, DnsOpcode.StandardQuery, false, false, request.RecursionDesired, false, false, false, DnsResponseCode.NoError, request.Question, null, authority, additional);
  415. }
  416. private DnsDatagram GetForwarderResponse(DnsDatagram request, AuthZone zone, AuthZone closestZone, ApexZone forwarderZone)
  417. {
  418. IReadOnlyList<DnsResourceRecord> authority = null;
  419. if (zone is not null)
  420. {
  421. if (zone.ContainsNameServerRecords())
  422. return GetReferralResponse(request, false, zone, forwarderZone);
  423. authority = zone.QueryRecords(DnsResourceRecordType.FWD, false);
  424. }
  425. if (((authority is null) || (authority.Count == 0)) && (closestZone is not null))
  426. {
  427. if (closestZone.ContainsNameServerRecords())
  428. return GetReferralResponse(request, false, closestZone, forwarderZone);
  429. authority = closestZone.QueryRecords(DnsResourceRecordType.FWD, false);
  430. }
  431. if ((authority is null) || (authority.Count == 0))
  432. {
  433. if (forwarderZone.ContainsNameServerRecords())
  434. return GetReferralResponse(request, false, forwarderZone, forwarderZone);
  435. authority = forwarderZone.QueryRecords(DnsResourceRecordType.FWD, false);
  436. }
  437. return new DnsDatagram(request.Identifier, true, DnsOpcode.StandardQuery, false, false, request.RecursionDesired, false, false, false, DnsResponseCode.NoError, request.Question, null, authority);
  438. }
  439. internal void Flush()
  440. {
  441. _zoneIndexLock.EnterWriteLock();
  442. try
  443. {
  444. _root.Clear();
  445. _zoneIndex.Clear();
  446. }
  447. finally
  448. {
  449. _zoneIndexLock.ExitWriteLock();
  450. }
  451. }
  452. private static IReadOnlyList<DnsResourceRecord> CondenseIncrementalZoneTransferRecords(string zoneName, DnsResourceRecord currentSoaRecord, IReadOnlyList<DnsResourceRecord> xfrRecords)
  453. {
  454. DnsResourceRecord firstSoaRecord = xfrRecords[0];
  455. DnsResourceRecord lastSoaRecord = xfrRecords[xfrRecords.Count - 1];
  456. DnsResourceRecord firstDeletedSoaRecord = null;
  457. DnsResourceRecord lastAddedSoaRecord = null;
  458. List<DnsResourceRecord> deletedRecords = new List<DnsResourceRecord>();
  459. List<DnsResourceRecord> deletedGlueRecords = new List<DnsResourceRecord>();
  460. List<DnsResourceRecord> addedRecords = new List<DnsResourceRecord>();
  461. List<DnsResourceRecord> addedGlueRecords = new List<DnsResourceRecord>();
  462. //read and apply difference sequences
  463. int index = 1;
  464. int count = xfrRecords.Count - 1;
  465. DnsSOARecordData currentSoa = (DnsSOARecordData)currentSoaRecord.RDATA;
  466. while (index < count)
  467. {
  468. //read deleted records
  469. DnsResourceRecord deletedSoaRecord = xfrRecords[index];
  470. if ((deletedSoaRecord.Type != DnsResourceRecordType.SOA) || !deletedSoaRecord.Name.Equals(zoneName, StringComparison.OrdinalIgnoreCase))
  471. throw new InvalidOperationException();
  472. if (firstDeletedSoaRecord is null)
  473. firstDeletedSoaRecord = deletedSoaRecord;
  474. index++;
  475. while (index < count)
  476. {
  477. DnsResourceRecord record = xfrRecords[index];
  478. if (record.Type == DnsResourceRecordType.SOA)
  479. break;
  480. if (zoneName.Length == 0)
  481. {
  482. //root zone case
  483. switch (record.Type)
  484. {
  485. case DnsResourceRecordType.A:
  486. case DnsResourceRecordType.AAAA:
  487. if (addedGlueRecords.Contains(record))
  488. addedGlueRecords.Remove(record);
  489. else
  490. deletedGlueRecords.Add(record);
  491. break;
  492. default:
  493. if (addedRecords.Contains(record))
  494. addedRecords.Remove(record);
  495. else
  496. deletedRecords.Add(record);
  497. break;
  498. }
  499. }
  500. else
  501. {
  502. if (record.Name.Equals(zoneName, StringComparison.OrdinalIgnoreCase) || record.Name.EndsWith("." + zoneName, StringComparison.OrdinalIgnoreCase))
  503. {
  504. if (addedRecords.Contains(record))
  505. addedRecords.Remove(record);
  506. else
  507. deletedRecords.Add(record);
  508. }
  509. else
  510. {
  511. switch (record.Type)
  512. {
  513. case DnsResourceRecordType.A:
  514. case DnsResourceRecordType.AAAA:
  515. if (addedGlueRecords.Contains(record))
  516. addedGlueRecords.Remove(record);
  517. else
  518. deletedGlueRecords.Add(record);
  519. break;
  520. }
  521. }
  522. }
  523. index++;
  524. }
  525. //read added records
  526. DnsResourceRecord addedSoaRecord = xfrRecords[index];
  527. if (!addedSoaRecord.Name.Equals(zoneName, StringComparison.OrdinalIgnoreCase))
  528. throw new InvalidOperationException();
  529. lastAddedSoaRecord = addedSoaRecord;
  530. index++;
  531. while (index < count)
  532. {
  533. DnsResourceRecord record = xfrRecords[index];
  534. if (record.Type == DnsResourceRecordType.SOA)
  535. break;
  536. if (zoneName.Length == 0)
  537. {
  538. //root zone case
  539. switch (record.Type)
  540. {
  541. case DnsResourceRecordType.A:
  542. case DnsResourceRecordType.AAAA:
  543. if (deletedGlueRecords.Contains(record))
  544. deletedGlueRecords.Remove(record);
  545. else
  546. addedGlueRecords.Add(record);
  547. break;
  548. default:
  549. if (deletedRecords.Contains(record))
  550. deletedRecords.Remove(record);
  551. else
  552. addedRecords.Add(record);
  553. break;
  554. }
  555. }
  556. else
  557. {
  558. if (record.Name.Equals(zoneName, StringComparison.OrdinalIgnoreCase) || record.Name.EndsWith("." + zoneName, StringComparison.OrdinalIgnoreCase))
  559. {
  560. if (deletedRecords.Contains(record))
  561. deletedRecords.Remove(record);
  562. else
  563. addedRecords.Add(record);
  564. }
  565. else
  566. {
  567. switch (record.Type)
  568. {
  569. case DnsResourceRecordType.A:
  570. case DnsResourceRecordType.AAAA:
  571. if (deletedGlueRecords.Contains(record))
  572. deletedGlueRecords.Remove(record);
  573. else
  574. addedGlueRecords.Add(record);
  575. break;
  576. }
  577. }
  578. }
  579. index++;
  580. }
  581. //check sequence soa serial
  582. DnsSOARecordData deletedSoa = deletedSoaRecord.RDATA as DnsSOARecordData;
  583. if (currentSoa.Serial != deletedSoa.Serial)
  584. throw new InvalidOperationException("Current SOA serial does not match with the IXFR difference sequence deleted SOA.");
  585. //check next difference sequence
  586. currentSoa = addedSoaRecord.RDATA as DnsSOARecordData;
  587. }
  588. //create condensed records
  589. List<DnsResourceRecord> condensedRecords = new List<DnsResourceRecord>(2 + 2 + deletedRecords.Count + deletedGlueRecords.Count + addedRecords.Count + addedGlueRecords.Count);
  590. condensedRecords.Add(firstSoaRecord);
  591. condensedRecords.Add(firstDeletedSoaRecord);
  592. condensedRecords.AddRange(deletedRecords);
  593. condensedRecords.AddRange(deletedGlueRecords);
  594. condensedRecords.Add(lastAddedSoaRecord);
  595. condensedRecords.AddRange(addedRecords);
  596. condensedRecords.AddRange(addedGlueRecords);
  597. condensedRecords.Add(lastSoaRecord);
  598. return condensedRecords;
  599. }
  600. #endregion
  601. #region public
  602. public void LoadAllZoneFiles()
  603. {
  604. Flush();
  605. string zonesFolder = Path.Combine(_dnsServer.ConfigFolder, "zones");
  606. if (!Directory.Exists(zonesFolder))
  607. Directory.CreateDirectory(zonesFolder);
  608. //move zone files to new folder
  609. {
  610. string[] oldZoneFiles = Directory.GetFiles(_dnsServer.ConfigFolder, "*.zone");
  611. foreach (string oldZoneFile in oldZoneFiles)
  612. File.Move(oldZoneFile, Path.Combine(zonesFolder, Path.GetFileName(oldZoneFile)));
  613. }
  614. //remove old internal zones files
  615. {
  616. string[] oldZoneFiles = new string[] { "localhost.zone", "1.0.0.127.in-addr.arpa.zone", "1.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.ip6.arpa.zone" };
  617. foreach (string oldZoneFile in oldZoneFiles)
  618. {
  619. string filePath = Path.Combine(zonesFolder, oldZoneFile);
  620. if (File.Exists(filePath))
  621. {
  622. try
  623. {
  624. File.Delete(filePath);
  625. }
  626. catch
  627. { }
  628. }
  629. }
  630. }
  631. //load system zones
  632. {
  633. {
  634. CreatePrimaryZone("localhost", _dnsServer.ServerDomain, true);
  635. SetRecords("localhost", "localhost", DnsResourceRecordType.A, 3600, new DnsResourceRecordData[] { new DnsARecordData(IPAddress.Loopback) });
  636. SetRecords("localhost", "localhost", DnsResourceRecordType.AAAA, 3600, new DnsResourceRecordData[] { new DnsAAAARecordData(IPAddress.IPv6Loopback) });
  637. }
  638. {
  639. string prtDomain = "0.in-addr.arpa";
  640. CreatePrimaryZone(prtDomain, _dnsServer.ServerDomain, true);
  641. }
  642. {
  643. string prtDomain = "255.in-addr.arpa";
  644. CreatePrimaryZone(prtDomain, _dnsServer.ServerDomain, true);
  645. }
  646. {
  647. string ptrZoneName = "127.in-addr.arpa";
  648. CreatePrimaryZone(ptrZoneName, _dnsServer.ServerDomain, true);
  649. SetRecords(ptrZoneName, "1.0.0.127.in-addr.arpa", DnsResourceRecordType.PTR, 3600, new DnsResourceRecordData[] { new DnsPTRRecordData("localhost") });
  650. }
  651. {
  652. string ptrZoneName = IPAddress.IPv6Loopback.GetReverseDomain();
  653. CreatePrimaryZone(ptrZoneName, _dnsServer.ServerDomain, true);
  654. SetRecords(ptrZoneName, ptrZoneName, DnsResourceRecordType.PTR, 3600, new DnsResourceRecordData[] { new DnsPTRRecordData("localhost") });
  655. }
  656. }
  657. //load zone files
  658. _zoneIndexLock.EnterWriteLock();
  659. try
  660. {
  661. string[] zoneFiles = Directory.GetFiles(zonesFolder, "*.zone");
  662. foreach (string zoneFile in zoneFiles)
  663. {
  664. try
  665. {
  666. using (FileStream fS = new FileStream(zoneFile, FileMode.Open, FileAccess.Read))
  667. {
  668. AuthZoneInfo zoneInfo = LoadZoneFrom(fS);
  669. _zoneIndex.Add(zoneInfo);
  670. }
  671. _dnsServer.LogManager?.Write("DNS Server successfully loaded zone file: " + zoneFile);
  672. }
  673. catch (Exception ex)
  674. {
  675. _dnsServer.LogManager?.Write("DNS Server failed to load zone file: " + zoneFile + "\r\n" + ex.ToString());
  676. }
  677. }
  678. _zoneIndex.Sort();
  679. }
  680. finally
  681. {
  682. _zoneIndexLock.ExitWriteLock();
  683. }
  684. }
  685. internal AuthZoneInfo CreateSpecialPrimaryZone(string zoneName, DnsSOARecordData soaRecord, DnsNSRecordData ns)
  686. {
  687. PrimaryZone apexZone = new PrimaryZone(_dnsServer, zoneName, soaRecord, ns);
  688. _zoneIndexLock.EnterWriteLock();
  689. try
  690. {
  691. if (_root.TryAdd(apexZone))
  692. {
  693. AuthZoneInfo zoneInfo = new AuthZoneInfo(apexZone);
  694. _zoneIndex.Add(zoneInfo);
  695. _zoneIndex.Sort();
  696. return zoneInfo;
  697. }
  698. }
  699. finally
  700. {
  701. _zoneIndexLock.ExitWriteLock();
  702. }
  703. return null;
  704. }
  705. internal void LoadSpecialPrimaryZones(IReadOnlyList<string> zoneNames, DnsSOARecordData soaRecord, DnsNSRecordData ns)
  706. {
  707. _zoneIndexLock.EnterWriteLock();
  708. try
  709. {
  710. foreach (string zoneName in zoneNames)
  711. {
  712. PrimaryZone apexZone = new PrimaryZone(_dnsServer, zoneName, soaRecord, ns);
  713. if (_root.TryAdd(apexZone))
  714. {
  715. AuthZoneInfo zoneInfo = new AuthZoneInfo(apexZone);
  716. _zoneIndex.Add(zoneInfo);
  717. }
  718. }
  719. _zoneIndex.Sort();
  720. }
  721. finally
  722. {
  723. _zoneIndexLock.ExitWriteLock();
  724. }
  725. }
  726. internal void LoadSpecialPrimaryZones(Func<string> getZoneName, DnsSOARecordData soaRecord, DnsNSRecordData ns)
  727. {
  728. _zoneIndexLock.EnterWriteLock();
  729. try
  730. {
  731. string zoneName;
  732. while (true)
  733. {
  734. zoneName = getZoneName();
  735. if (zoneName is null)
  736. break;
  737. PrimaryZone apexZone = new PrimaryZone(_dnsServer, zoneName, soaRecord, ns);
  738. if (_root.TryAdd(apexZone))
  739. {
  740. AuthZoneInfo zoneInfo = new AuthZoneInfo(apexZone);
  741. _zoneIndex.Add(zoneInfo);
  742. }
  743. }
  744. _zoneIndex.Sort();
  745. }
  746. finally
  747. {
  748. _zoneIndexLock.ExitWriteLock();
  749. }
  750. }
  751. public AuthZoneInfo CreatePrimaryZone(string zoneName, string primaryNameServer, bool @internal)
  752. {
  753. PrimaryZone apexZone = new PrimaryZone(_dnsServer, zoneName, primaryNameServer, @internal);
  754. _zoneIndexLock.EnterWriteLock();
  755. try
  756. {
  757. if (_root.TryAdd(apexZone))
  758. {
  759. AuthZoneInfo zoneInfo = new AuthZoneInfo(apexZone);
  760. _zoneIndex.Add(zoneInfo);
  761. _zoneIndex.Sort();
  762. return zoneInfo;
  763. }
  764. }
  765. finally
  766. {
  767. _zoneIndexLock.ExitWriteLock();
  768. }
  769. return null;
  770. }
  771. public async Task<AuthZoneInfo> CreateSecondaryZoneAsync(string zoneName, string primaryNameServerAddresses = null, DnsTransportProtocol zoneTransferProtocol = DnsTransportProtocol.Tcp, string tsigKeyName = null)
  772. {
  773. SecondaryZone apexZone = await SecondaryZone.CreateAsync(_dnsServer, zoneName, primaryNameServerAddresses, zoneTransferProtocol, tsigKeyName);
  774. _zoneIndexLock.EnterWriteLock();
  775. try
  776. {
  777. if (_root.TryAdd(apexZone))
  778. {
  779. apexZone.TriggerRefresh(0);
  780. AuthZoneInfo zoneInfo = new AuthZoneInfo(apexZone);
  781. _zoneIndex.Add(zoneInfo);
  782. _zoneIndex.Sort();
  783. return zoneInfo;
  784. }
  785. }
  786. finally
  787. {
  788. _zoneIndexLock.ExitWriteLock();
  789. }
  790. return null;
  791. }
  792. public async Task<AuthZoneInfo> CreateStubZoneAsync(string zoneName, string primaryNameServerAddresses = null)
  793. {
  794. StubZone apexZone = await StubZone.CreateAsync(_dnsServer, zoneName, primaryNameServerAddresses);
  795. _zoneIndexLock.EnterWriteLock();
  796. try
  797. {
  798. if (_root.TryAdd(apexZone))
  799. {
  800. apexZone.TriggerRefresh(0);
  801. AuthZoneInfo zoneInfo = new AuthZoneInfo(apexZone);
  802. _zoneIndex.Add(zoneInfo);
  803. _zoneIndex.Sort();
  804. return zoneInfo;
  805. }
  806. }
  807. finally
  808. {
  809. _zoneIndexLock.ExitWriteLock();
  810. }
  811. return null;
  812. }
  813. public AuthZoneInfo CreateForwarderZone(string zoneName, DnsTransportProtocol forwarderProtocol, string forwarder, bool dnssecValidation, NetProxyType proxyType, string proxyAddress, ushort proxyPort, string proxyUsername, string proxyPassword, string fwdRecordComments)
  814. {
  815. ForwarderZone apexZone = new ForwarderZone(zoneName, forwarderProtocol, forwarder, dnssecValidation, proxyType, proxyAddress, proxyPort, proxyUsername, proxyPassword, fwdRecordComments);
  816. _zoneIndexLock.EnterWriteLock();
  817. try
  818. {
  819. if (_root.TryAdd(apexZone))
  820. {
  821. AuthZoneInfo zoneInfo = new AuthZoneInfo(apexZone);
  822. _zoneIndex.Add(zoneInfo);
  823. _zoneIndex.Sort();
  824. return zoneInfo;
  825. }
  826. }
  827. finally
  828. {
  829. _zoneIndexLock.ExitWriteLock();
  830. }
  831. return null;
  832. }
  833. public void ConvertZoneType(string zoneName, AuthZoneType type)
  834. {
  835. AuthZoneInfo currentZoneInfo = GetAuthZoneInfo(zoneName, false);
  836. if (currentZoneInfo is null)
  837. throw new DnsServerException("No such zone was found: " + (zoneName.Length == 0 ? "." : zoneName));
  838. if (currentZoneInfo.Type == type)
  839. throw new DnsServerException("Cannot convert the zone '" + (zoneName.Length == 0 ? "." : zoneName) + "' from " + currentZoneInfo.Type.ToString() + " to " + type.ToString() + " zone: the zone is already of the same type.");
  840. switch (currentZoneInfo.Type)
  841. {
  842. case AuthZoneType.Primary:
  843. switch (type)
  844. {
  845. case AuthZoneType.Forwarder:
  846. if (currentZoneInfo.DnssecStatus != AuthZoneDnssecStatus.Unsigned)
  847. throw new DnsServerException("Cannot convert the zone '" + (zoneName.Length == 0 ? "." : zoneName) + "' from " + currentZoneInfo.Type.ToString() + " to " + type + " zone: converting the zone will cause lose of DNSSEC private keys.");
  848. break;
  849. default:
  850. throw new DnsServerException("Cannot convert the zone '" + (zoneName.Length == 0 ? "." : zoneName) + "' from " + currentZoneInfo.Type.ToString() + " to " + type + " zone: not supported.");
  851. }
  852. break;
  853. case AuthZoneType.Secondary:
  854. switch (type)
  855. {
  856. case AuthZoneType.Primary:
  857. case AuthZoneType.Forwarder:
  858. break;
  859. default:
  860. throw new DnsServerException("Cannot convert the zone '" + (zoneName.Length == 0 ? "." : zoneName) + "' from " + currentZoneInfo.Type.ToString() + " to " + type + " zone: not supported.");
  861. }
  862. break;
  863. case AuthZoneType.Forwarder:
  864. switch (type)
  865. {
  866. case AuthZoneType.Primary:
  867. break;
  868. default:
  869. throw new DnsServerException("Cannot convert the zone '" + (zoneName.Length == 0 ? "." : zoneName) + "' from " + currentZoneInfo.Type.ToString() + " to " + type + " zone: not supported.");
  870. }
  871. break;
  872. default:
  873. throw new DnsServerException("Cannot convert the zone '" + (zoneName.Length == 0 ? "." : zoneName) + "' from " + currentZoneInfo.Type.ToString() + " to " + type.ToString() + " zone: not supported.");
  874. }
  875. //read all current records
  876. List<DnsResourceRecord> allRecords = new List<DnsResourceRecord>();
  877. ListAllZoneRecords(zoneName, allRecords);
  878. try
  879. {
  880. //delete current zone
  881. DeleteZone(zoneName);
  882. //create new zone
  883. AuthZoneInfo newZoneInfo;
  884. switch (type)
  885. {
  886. case AuthZoneType.Primary:
  887. switch (currentZoneInfo.Type)
  888. {
  889. case AuthZoneType.Secondary:
  890. {
  891. //reset SOA metadata and remove DNSSEC records
  892. List<DnsResourceRecord> updateRecords = new List<DnsResourceRecord>(allRecords.Count);
  893. foreach (DnsResourceRecord record in allRecords)
  894. {
  895. switch (record.Type)
  896. {
  897. case DnsResourceRecordType.SOA:
  898. {
  899. AuthRecordInfo recordInfo = record.GetAuthRecordInfo();
  900. record.Tag = null;
  901. AuthRecordInfo newRecordInfo = record.GetAuthRecordInfo();
  902. newRecordInfo.Comments = recordInfo.Comments;
  903. }
  904. break;
  905. case DnsResourceRecordType.DNSKEY:
  906. case DnsResourceRecordType.RRSIG:
  907. case DnsResourceRecordType.NSEC:
  908. case DnsResourceRecordType.NSEC3:
  909. case DnsResourceRecordType.NSEC3PARAM:
  910. continue;
  911. }
  912. updateRecords.Add(record);
  913. }
  914. allRecords = updateRecords;
  915. }
  916. break;
  917. case AuthZoneType.Forwarder:
  918. {
  919. //remove all FWD records
  920. List<DnsResourceRecord> updateRecords = new List<DnsResourceRecord>(allRecords.Count);
  921. foreach (DnsResourceRecord record in allRecords)
  922. {
  923. if (record.Type == DnsResourceRecordType.FWD)
  924. continue;
  925. updateRecords.Add(record);
  926. }
  927. allRecords = updateRecords;
  928. }
  929. break;
  930. }
  931. newZoneInfo = CreatePrimaryZone(zoneName, _dnsServer.ServerDomain, false);
  932. break;
  933. case AuthZoneType.Forwarder:
  934. switch (currentZoneInfo.Type)
  935. {
  936. case AuthZoneType.Primary:
  937. {
  938. //remove SOA and NS records
  939. List<DnsResourceRecord> updateRecords = new List<DnsResourceRecord>(allRecords.Count);
  940. foreach (DnsResourceRecord record in allRecords)
  941. {
  942. switch (record.Type)
  943. {
  944. case DnsResourceRecordType.SOA:
  945. case DnsResourceRecordType.NS:
  946. continue;
  947. }
  948. updateRecords.Add(record);
  949. }
  950. allRecords = updateRecords;
  951. }
  952. break;
  953. case AuthZoneType.Secondary:
  954. {
  955. //remove SOA, NS and DNSSEC records
  956. List<DnsResourceRecord> updateRecords = new List<DnsResourceRecord>(allRecords.Count);
  957. foreach (DnsResourceRecord record in allRecords)
  958. {
  959. switch (record.Type)
  960. {
  961. case DnsResourceRecordType.SOA:
  962. case DnsResourceRecordType.NS:
  963. case DnsResourceRecordType.DNSKEY:
  964. case DnsResourceRecordType.RRSIG:
  965. case DnsResourceRecordType.NSEC:
  966. case DnsResourceRecordType.NSEC3:
  967. case DnsResourceRecordType.NSEC3PARAM:
  968. continue;
  969. }
  970. updateRecords.Add(record);
  971. }
  972. allRecords = updateRecords;
  973. }
  974. break;
  975. }
  976. newZoneInfo = CreateForwarderZone(zoneName, DnsTransportProtocol.Udp, "this-server", _dnsServer.DnssecValidation, NetProxyType.None, null, 0, null, null, null);
  977. break;
  978. default:
  979. throw new InvalidOperationException();
  980. }
  981. //load records
  982. LoadRecords(newZoneInfo.ApexZone, allRecords);
  983. }
  984. catch (Exception ex)
  985. {
  986. _dnsServer.LogManager?.Write("DNS Server failed to convert the zone '" + (zoneName.Length == 0 ? "." : zoneName) + "' from " + currentZoneInfo.Type.ToString() + " to " + type.ToString() + " zone.\r\n" + ex.ToString());
  987. //delete the zone if it was created
  988. DeleteZone(zoneName);
  989. //reload old zone file
  990. string zoneFile = Path.Combine(_dnsServer.ConfigFolder, "zones", zoneName + ".zone");
  991. _zoneIndexLock.EnterWriteLock();
  992. try
  993. {
  994. using (FileStream fS = new FileStream(zoneFile, FileMode.Open, FileAccess.Read))
  995. {
  996. AuthZoneInfo zoneInfo = LoadZoneFrom(fS);
  997. _zoneIndex.Add(zoneInfo);
  998. _zoneIndex.Sort();
  999. }
  1000. _dnsServer.LogManager?.Write("DNS Server successfully loaded zone file: " + zoneFile);
  1001. }
  1002. catch (Exception ex2)
  1003. {
  1004. _dnsServer.LogManager?.Write("DNS Server failed to load zone file: " + zoneFile + "\r\n" + ex2.ToString());
  1005. }
  1006. finally
  1007. {
  1008. _zoneIndexLock.ExitWriteLock();
  1009. }
  1010. throw;
  1011. }
  1012. }
  1013. public void SignPrimaryZoneWithRsaNSEC(string zoneName, string hashAlgorithm, int kskKeySize, int zskKeySize, uint dnsKeyTtl, ushort zskRolloverDays)
  1014. {
  1015. if (!_root.TryGet(zoneName, out ApexZone apexZone) || (apexZone is not PrimaryZone primaryZone) || primaryZone.Internal)
  1016. throw new DnsServerException("No such primary zone was found: " + zoneName);
  1017. primaryZone.SignZoneWithRsaNSec(hashAlgorithm, kskKeySize, zskKeySize, dnsKeyTtl, zskRolloverDays);
  1018. }
  1019. public void SignPrimaryZoneWithRsaNSEC3(string zoneName, string hashAlgorithm, int kskKeySize, int zskKeySize, ushort iterations, byte saltLength, uint dnsKeyTtl, ushort zskRolloverDays)
  1020. {
  1021. if (!_root.TryGet(zoneName, out ApexZone apexZone) || (apexZone is not PrimaryZone primaryZone) || primaryZone.Internal)
  1022. throw new DnsServerException("No such primary zone was found: " + zoneName);
  1023. primaryZone.SignZoneWithRsaNSec3(hashAlgorithm, kskKeySize, zskKeySize, iterations, saltLength, dnsKeyTtl, zskRolloverDays);
  1024. }
  1025. public void SignPrimaryZoneWithEcdsaNSEC(string zoneName, string curve, uint dnsKeyTtl, ushort zskRolloverDays)
  1026. {
  1027. if (!_root.TryGet(zoneName, out ApexZone apexZone) || (apexZone is not PrimaryZone primaryZone) || primaryZone.Internal)
  1028. throw new DnsServerException("No such primary zone was found: " + zoneName);
  1029. primaryZone.SignZoneWithEcdsaNSec(curve, dnsKeyTtl, zskRolloverDays);
  1030. }
  1031. public void SignPrimaryZoneWithEcdsaNSEC3(string zoneName, string curve, ushort iterations, byte saltLength, uint dnsKeyTtl, ushort zskRolloverDays)
  1032. {
  1033. if (!_root.TryGet(zoneName, out ApexZone apexZone) || (apexZone is not PrimaryZone primaryZone) || primaryZone.Internal)
  1034. throw new DnsServerException("No such primary zone was found: " + zoneName);
  1035. primaryZone.SignZoneWithEcdsaNSec3(curve, iterations, saltLength, dnsKeyTtl, zskRolloverDays);
  1036. }
  1037. public void UnsignPrimaryZone(string zoneName)
  1038. {
  1039. if (!_root.TryGet(zoneName, out ApexZone apexZone) || (apexZone is not PrimaryZone primaryZone) || primaryZone.Internal)
  1040. throw new DnsServerException("No such primary zone was found: " + zoneName);
  1041. primaryZone.UnsignZone();
  1042. }
  1043. public void ConvertPrimaryZoneToNSEC(string zoneName)
  1044. {
  1045. if (!_root.TryGet(zoneName, out ApexZone apexZone) || (apexZone is not PrimaryZone primaryZone) || primaryZone.Internal)
  1046. throw new DnsServerException("No such primary zone was found: " + zoneName);
  1047. primaryZone.ConvertToNSec();
  1048. }
  1049. public void ConvertPrimaryZoneToNSEC3(string zoneName, ushort iterations, byte saltLength)
  1050. {
  1051. if (!_root.TryGet(zoneName, out ApexZone apexZone) || (apexZone is not PrimaryZone primaryZone) || primaryZone.Internal)
  1052. throw new DnsServerException("No such primary zone was found: " + zoneName);
  1053. primaryZone.ConvertToNSec3(iterations, saltLength);
  1054. }
  1055. public void UpdatePrimaryZoneNSEC3Parameters(string zoneName, ushort iterations, byte saltLength)
  1056. {
  1057. if (!_root.TryGet(zoneName, out ApexZone apexZone) || (apexZone is not PrimaryZone primaryZone) || primaryZone.Internal)
  1058. throw new DnsServerException("No such primary zone was found: " + zoneName);
  1059. primaryZone.UpdateNSec3Parameters(iterations, saltLength);
  1060. }
  1061. public void UpdatePrimaryZoneDnsKeyTtl(string zoneName, uint dnsKeyTtl)
  1062. {
  1063. if (!_root.TryGet(zoneName, out ApexZone apexZone) || (apexZone is not PrimaryZone primaryZone) || primaryZone.Internal)
  1064. throw new DnsServerException("No such primary zone was found: " + zoneName);
  1065. primaryZone.UpdateDnsKeyTtl(dnsKeyTtl);
  1066. }
  1067. public void GenerateAndAddPrimaryZoneDnssecRsaPrivateKey(string zoneName, DnssecPrivateKeyType keyType, string hashAlgorithm, int keySize, ushort rolloverDays)
  1068. {
  1069. if (!_root.TryGet(zoneName, out ApexZone apexZone) || (apexZone is not PrimaryZone primaryZone) || primaryZone.Internal)
  1070. throw new DnsServerException("No such primary zone was found: " + zoneName);
  1071. primaryZone.GenerateAndAddRsaKey(keyType, hashAlgorithm, keySize, rolloverDays);
  1072. }
  1073. public void GenerateAndAddPrimaryZoneDnssecEcdsaPrivateKey(string zoneName, DnssecPrivateKeyType keyType, string curve, ushort rolloverDays)
  1074. {
  1075. if (!_root.TryGet(zoneName, out ApexZone apexZone) || (apexZone is not PrimaryZone primaryZone) || primaryZone.Internal)
  1076. throw new DnsServerException("No such primary zone was found: " + zoneName);
  1077. primaryZone.GenerateAndAddEcdsaKey(keyType, curve, rolloverDays);
  1078. }
  1079. public void UpdatePrimaryZoneDnssecPrivateKey(string zoneName, ushort keyTag, ushort rolloverDays)
  1080. {
  1081. if (!_root.TryGet(zoneName, out ApexZone apexZone) || (apexZone is not PrimaryZone primaryZone) || primaryZone.Internal)
  1082. throw new DnsServerException("No such primary zone was found: " + zoneName);
  1083. primaryZone.UpdatePrivateKey(keyTag, rolloverDays);
  1084. }
  1085. public void DeletePrimaryZoneDnssecPrivateKey(string zoneName, ushort keyTag)
  1086. {
  1087. if (!_root.TryGet(zoneName, out ApexZone apexZone) || (apexZone is not PrimaryZone primaryZone) || primaryZone.Internal)
  1088. throw new DnsServerException("No such primary zone was found: " + zoneName);
  1089. primaryZone.DeletePrivateKey(keyTag);
  1090. }
  1091. public void PublishAllGeneratedPrimaryZoneDnssecPrivateKeys(string zoneName)
  1092. {
  1093. if (!_root.TryGet(zoneName, out ApexZone apexZone) || (apexZone is not PrimaryZone primaryZone) || primaryZone.Internal)
  1094. throw new DnsServerException("No such primary zone was found: " + zoneName);
  1095. primaryZone.PublishAllGeneratedKeys();
  1096. }
  1097. public void RolloverPrimaryZoneDnsKey(string zoneName, ushort keyTag)
  1098. {
  1099. if (!_root.TryGet(zoneName, out ApexZone apexZone) || (apexZone is not PrimaryZone primaryZone) || primaryZone.Internal)
  1100. throw new DnsServerException("No such primary zone was found: " + zoneName);
  1101. primaryZone.RolloverDnsKey(keyTag);
  1102. }
  1103. public void RetirePrimaryZoneDnsKey(string zoneName, ushort keyTag)
  1104. {
  1105. if (!_root.TryGet(zoneName, out ApexZone apexZone) || (apexZone is not PrimaryZone primaryZone) || primaryZone.Internal)
  1106. throw new DnsServerException("No such primary zone was found: " + zoneName);
  1107. primaryZone.RetireDnsKey(keyTag);
  1108. }
  1109. public bool DeleteZone(string zoneName)
  1110. {
  1111. _zoneIndexLock.EnterWriteLock();
  1112. try
  1113. {
  1114. if (_root.TryRemove(zoneName, out ApexZone apexZone))
  1115. {
  1116. apexZone.Dispose();
  1117. AuthZoneInfo zoneInfo = new AuthZoneInfo(apexZone);
  1118. if (!_zoneIndex.Remove(zoneInfo))
  1119. throw new InvalidOperationException("Zone deleted from tree but failed to remove from zone index.");
  1120. return true;
  1121. }
  1122. }
  1123. finally
  1124. {
  1125. _zoneIndexLock.ExitWriteLock();
  1126. }
  1127. return false;
  1128. }
  1129. public AuthZoneInfo GetAuthZoneInfo(string zoneName, bool loadHistory = false)
  1130. {
  1131. if (_root.TryGet(zoneName, out AuthZoneNode authZoneNode) && (authZoneNode.ApexZone is not null))
  1132. return new AuthZoneInfo(authZoneNode.ApexZone, loadHistory);
  1133. return null;
  1134. }
  1135. public AuthZoneInfo FindAuthZoneInfo(string domain, bool loadHistory = false)
  1136. {
  1137. _ = _root.FindZone(domain, out _, out _, out ApexZone apexZone, out _);
  1138. if (apexZone is null)
  1139. return null;
  1140. return new AuthZoneInfo(apexZone, loadHistory);
  1141. }
  1142. public bool NameExists(string zoneName, string domain)
  1143. {
  1144. ValidateZoneNameFor(zoneName, domain);
  1145. return _root.TryGet(zoneName, domain, out _);
  1146. }
  1147. public void ListAllZoneRecords(string zoneName, List<DnsResourceRecord> records)
  1148. {
  1149. foreach (AuthZone zone in _root.GetZoneWithSubDomainZones(zoneName))
  1150. zone.ListAllRecords(records);
  1151. }
  1152. public void ListAllRecords(string zoneName, string domain, List<DnsResourceRecord> records)
  1153. {
  1154. ValidateZoneNameFor(zoneName, domain);
  1155. if (_root.TryGet(zoneName, domain, out AuthZone authZone))
  1156. authZone.ListAllRecords(records);
  1157. }
  1158. public IReadOnlyList<DnsResourceRecord> GetRecords(string zoneName, string domain, DnsResourceRecordType type)
  1159. {
  1160. ValidateZoneNameFor(zoneName, domain);
  1161. if (_root.TryGet(zoneName, domain, out AuthZone authZone))
  1162. return authZone.GetRecords(type);
  1163. return Array.Empty<DnsResourceRecord>();
  1164. }
  1165. public IReadOnlyDictionary<DnsResourceRecordType, IReadOnlyList<DnsResourceRecord>> GetAllRecords(string zoneName, string domain)
  1166. {
  1167. ValidateZoneNameFor(zoneName, domain);
  1168. if (_root.TryGet(zoneName, domain, out AuthZone authZone))
  1169. return authZone.GetAllRecords();
  1170. return new Dictionary<DnsResourceRecordType, IReadOnlyList<DnsResourceRecord>>(1);
  1171. }
  1172. public IReadOnlyList<DnsResourceRecord> QueryZoneTransferRecords(string zoneName)
  1173. {
  1174. AuthZoneInfo authZone = GetAuthZoneInfo(zoneName, false);
  1175. if (authZone is null)
  1176. throw new InvalidOperationException("Zone was not found: " + zoneName);
  1177. //only primary and secondary zones support zone transfer
  1178. IReadOnlyList<DnsResourceRecord> soaRecords = authZone.GetApexRecords(DnsResourceRecordType.SOA);
  1179. if (soaRecords.Count != 1)
  1180. throw new InvalidOperationException("Zone must be a primary or secondary zone.");
  1181. DnsResourceRecord soaRecord = soaRecords[0];
  1182. List<DnsResourceRecord> records = new List<DnsResourceRecord>();
  1183. ListAllZoneRecords(zoneName, records);
  1184. List<DnsResourceRecord> xfrRecords = new List<DnsResourceRecord>(records.Count + 1);
  1185. //start message
  1186. xfrRecords.Add(soaRecord);
  1187. foreach (DnsResourceRecord record in records)
  1188. {
  1189. AuthRecordInfo authRecordInfo = record.GetAuthRecordInfo();
  1190. if (authRecordInfo.Disabled)
  1191. continue;
  1192. switch (record.Type)
  1193. {
  1194. case DnsResourceRecordType.SOA:
  1195. break; //skip record
  1196. case DnsResourceRecordType.NS:
  1197. xfrRecords.Add(record);
  1198. IReadOnlyList<DnsResourceRecord> glueRecords = authRecordInfo.GlueRecords;
  1199. if (glueRecords is not null)
  1200. {
  1201. foreach (DnsResourceRecord glueRecord in glueRecords)
  1202. xfrRecords.Add(glueRecord);
  1203. }
  1204. break;
  1205. default:
  1206. xfrRecords.Add(record);
  1207. break;
  1208. }
  1209. }
  1210. //end message
  1211. xfrRecords.Add(soaRecord);
  1212. return xfrRecords;
  1213. }
  1214. public IReadOnlyList<DnsResourceRecord> QueryIncrementalZoneTransferRecords(string zoneName, DnsResourceRecord clientSoaRecord)
  1215. {
  1216. AuthZoneInfo authZone = GetAuthZoneInfo(zoneName, true);
  1217. if (authZone is null)
  1218. throw new InvalidOperationException("Zone was not found: " + zoneName);
  1219. //only primary and secondary zones support zone transfer
  1220. IReadOnlyList<DnsResourceRecord> soaRecords = authZone.GetApexRecords(DnsResourceRecordType.SOA);
  1221. if (soaRecords.Count != 1)
  1222. throw new InvalidOperationException("Zone must be a primary or secondary zone.");
  1223. DnsResourceRecord currentSoaRecord = soaRecords[0];
  1224. uint clientSerial = (clientSoaRecord.RDATA as DnsSOARecordData).Serial;
  1225. if (clientSerial == (currentSoaRecord.RDATA as DnsSOARecordData).Serial)
  1226. {
  1227. //zone not modified
  1228. return new DnsResourceRecord[] { currentSoaRecord };
  1229. }
  1230. //find history record start from client serial
  1231. IReadOnlyList<DnsResourceRecord> zoneHistory = authZone.ZoneHistory;
  1232. int index = 0;
  1233. while (index < zoneHistory.Count)
  1234. {
  1235. //check difference sequence
  1236. if ((zoneHistory[index].RDATA as DnsSOARecordData).Serial == clientSerial)
  1237. break; //found history for client's serial
  1238. //skip to next difference sequence
  1239. index++;
  1240. int soaCount = 1;
  1241. while (index < zoneHistory.Count)
  1242. {
  1243. if (zoneHistory[index].Type == DnsResourceRecordType.SOA)
  1244. {
  1245. soaCount++;
  1246. if (soaCount == 3)
  1247. break;
  1248. }
  1249. index++;
  1250. }
  1251. }
  1252. if (index == zoneHistory.Count)
  1253. {
  1254. //client's serial was not found in zone history
  1255. //do full zone transfer
  1256. return QueryZoneTransferRecords(zoneName);
  1257. }
  1258. List<DnsResourceRecord> xfrRecords = new List<DnsResourceRecord>();
  1259. //start incremental message
  1260. xfrRecords.Add(currentSoaRecord);
  1261. //write history
  1262. for (int i = index; i < zoneHistory.Count; i++)
  1263. xfrRecords.Add(zoneHistory[i]);
  1264. //end incremental message
  1265. xfrRecords.Add(currentSoaRecord);
  1266. //condense
  1267. return CondenseIncrementalZoneTransferRecords(zoneName, clientSoaRecord, xfrRecords);
  1268. }
  1269. public void SyncZoneTransferRecords(string zoneName, IReadOnlyList<DnsResourceRecord> xfrRecords)
  1270. {
  1271. if ((xfrRecords.Count < 2) || (xfrRecords[0].Type != DnsResourceRecordType.SOA) || !xfrRecords[0].Name.Equals(zoneName, StringComparison.OrdinalIgnoreCase) || !xfrRecords[xfrRecords.Count - 1].Equals(xfrRecords[0]))
  1272. throw new DnsServerException("Invalid AXFR response was received.");
  1273. List<DnsResourceRecord> latestRecords = new List<DnsResourceRecord>(xfrRecords.Count);
  1274. List<DnsResourceRecord> allGlueRecords = new List<DnsResourceRecord>(4);
  1275. if (zoneName.Length == 0)
  1276. {
  1277. //root zone case
  1278. for (int i = 1; i < xfrRecords.Count; i++)
  1279. {
  1280. DnsResourceRecord record = xfrRecords[i];
  1281. switch (record.Type)
  1282. {
  1283. case DnsResourceRecordType.A:
  1284. case DnsResourceRecordType.AAAA:
  1285. if (!allGlueRecords.Contains(record))
  1286. allGlueRecords.Add(record);
  1287. break;
  1288. default:
  1289. if (!latestRecords.Contains(record))
  1290. latestRecords.Add(record);
  1291. break;
  1292. }
  1293. }
  1294. }
  1295. else
  1296. {
  1297. for (int i = 1; i < xfrRecords.Count; i++)
  1298. {
  1299. DnsResourceRecord record = xfrRecords[i];
  1300. if (record.Name.Equals(zoneName, StringComparison.OrdinalIgnoreCase) || record.Name.EndsWith("." + zoneName, StringComparison.OrdinalIgnoreCase))
  1301. {
  1302. if (!latestRecords.Contains(record))
  1303. latestRecords.Add(record);
  1304. }
  1305. else if (!allGlueRecords.Contains(record))
  1306. {
  1307. allGlueRecords.Add(record);
  1308. }
  1309. }
  1310. }
  1311. if (allGlueRecords.Count > 0)
  1312. {
  1313. foreach (DnsResourceRecord record in latestRecords)
  1314. {
  1315. if (record.Type == DnsResourceRecordType.NS)
  1316. record.SyncGlueRecords(allGlueRecords);
  1317. }
  1318. }
  1319. //sync records
  1320. List<DnsResourceRecord> currentRecords = new List<DnsResourceRecord>();
  1321. ListAllZoneRecords(zoneName, currentRecords);
  1322. Dictionary<string, Dictionary<DnsResourceRecordType, List<DnsResourceRecord>>> currentRecordsGroupedByDomain = DnsResourceRecord.GroupRecords(currentRecords);
  1323. Dictionary<string, Dictionary<DnsResourceRecordType, List<DnsResourceRecord>>> latestRecordsGroupedByDomain = DnsResourceRecord.GroupRecords(latestRecords);
  1324. //remove domains that do not exists in new records
  1325. foreach (KeyValuePair<string, Dictionary<DnsResourceRecordType, List<DnsResourceRecord>>> currentDomain in currentRecordsGroupedByDomain)
  1326. {
  1327. if (!latestRecordsGroupedByDomain.ContainsKey(currentDomain.Key))
  1328. _root.TryRemove(currentDomain.Key, out SubDomainZone _);
  1329. }
  1330. //sync new records
  1331. foreach (KeyValuePair<string, Dictionary<DnsResourceRecordType, List<DnsResourceRecord>>> latestEntries in latestRecordsGroupedByDomain)
  1332. {
  1333. AuthZone zone = GetOrAddSubDomainZone(zoneName, latestEntries.Key);
  1334. if (zone.Name.Equals(zoneName, StringComparison.OrdinalIgnoreCase))
  1335. zone.SyncRecords(latestEntries.Value);
  1336. else if ((zone is SubDomainZone subDomainZone) && subDomainZone.AuthoritativeZone.Name.Equals(zoneName, StringComparison.OrdinalIgnoreCase))
  1337. zone.SyncRecords(latestEntries.Value);
  1338. }
  1339. if (_root.TryGet(zoneName, out ApexZone apexZone))
  1340. apexZone.UpdateDnssecStatus();
  1341. }
  1342. public IReadOnlyList<DnsResourceRecord> SyncIncrementalZoneTransferRecords(string zoneName, IReadOnlyList<DnsResourceRecord> xfrRecords)
  1343. {
  1344. if ((xfrRecords.Count < 2) || (xfrRecords[0].Type != DnsResourceRecordType.SOA) || !xfrRecords[0].Name.Equals(zoneName, StringComparison.OrdinalIgnoreCase) || !xfrRecords[xfrRecords.Count - 1].Equals(xfrRecords[0]))
  1345. throw new DnsServerException("Invalid IXFR/AXFR response was received.");
  1346. if ((xfrRecords.Count < 4) || (xfrRecords[1].Type != DnsResourceRecordType.SOA))
  1347. {
  1348. //received AXFR response
  1349. SyncZoneTransferRecords(zoneName, xfrRecords);
  1350. return Array.Empty<DnsResourceRecord>();
  1351. }
  1352. if (!_root.TryGet(zoneName, out ApexZone apexZone))
  1353. throw new InvalidOperationException("No such zone was found: " + zoneName);
  1354. IReadOnlyList<DnsResourceRecord> soaRecords = apexZone.GetRecords(DnsResourceRecordType.SOA);
  1355. if (soaRecords.Count != 1)
  1356. throw new InvalidOperationException("No authoritative zone was found: " + zoneName);
  1357. //process IXFR response
  1358. DnsResourceRecord currentSoaRecord = soaRecords[0];
  1359. DnsSOARecordData currentSoa = currentSoaRecord.RDATA as DnsSOARecordData;
  1360. IReadOnlyList<DnsResourceRecord> condensedXfrRecords = CondenseIncrementalZoneTransferRecords(zoneName, currentSoaRecord, xfrRecords);
  1361. List<DnsResourceRecord> deletedRecords = new List<DnsResourceRecord>();
  1362. List<DnsResourceRecord> deletedGlueRecords = new List<DnsResourceRecord>();
  1363. List<DnsResourceRecord> addedRecords = new List<DnsResourceRecord>();
  1364. List<DnsResourceRecord> addedGlueRecords = new List<DnsResourceRecord>();
  1365. //read and apply difference sequences
  1366. int index = 1;
  1367. int count = condensedXfrRecords.Count - 1;
  1368. while (index < count)
  1369. {
  1370. //read deleted records
  1371. DnsResourceRecord deletedSoaRecord = condensedXfrRecords[index];
  1372. if ((deletedSoaRecord.Type != DnsResourceRecordType.SOA) || !deletedSoaRecord.Name.Equals(zoneName, StringComparison.OrdinalIgnoreCase))
  1373. throw new InvalidOperationException();
  1374. index++;
  1375. while (index < count)
  1376. {
  1377. DnsResourceRecord record = condensedXfrRecords[index];
  1378. if (record.Type == DnsResourceRecordType.SOA)
  1379. break;
  1380. if (zoneName.Length == 0)
  1381. {
  1382. //root zone case
  1383. switch (record.Type)
  1384. {
  1385. case DnsResourceRecordType.A:
  1386. case DnsResourceRecordType.AAAA:
  1387. deletedGlueRecords.Add(record);
  1388. break;
  1389. default:
  1390. deletedRecords.Add(record);
  1391. break;
  1392. }
  1393. }
  1394. else
  1395. {
  1396. if (record.Name.Equals(zoneName, StringComparison.OrdinalIgnoreCase) || record.Name.EndsWith("." + zoneName, StringComparison.OrdinalIgnoreCase))
  1397. {
  1398. deletedRecords.Add(record);
  1399. }
  1400. else
  1401. {
  1402. switch (record.Type)
  1403. {
  1404. case DnsResourceRecordType.A:
  1405. case DnsResourceRecordType.AAAA:
  1406. deletedGlueRecords.Add(record);
  1407. break;
  1408. }
  1409. }
  1410. }
  1411. index++;
  1412. }
  1413. //read added records
  1414. DnsResourceRecord addedSoaRecord = condensedXfrRecords[index];
  1415. if (!addedSoaRecord.Name.Equals(zoneName, StringComparison.OrdinalIgnoreCase))
  1416. throw new InvalidOperationException();
  1417. index++;
  1418. while (index < count)
  1419. {
  1420. DnsResourceRecord record = condensedXfrRecords[index];
  1421. if (record.Type == DnsResourceRecordType.SOA)
  1422. break;
  1423. if (zoneName.Length == 0)
  1424. {
  1425. //root zone case
  1426. switch (record.Type)
  1427. {
  1428. case DnsResourceRecordType.A:
  1429. case DnsResourceRecordType.AAAA:
  1430. addedGlueRecords.Add(record);
  1431. break;
  1432. default:
  1433. addedRecords.Add(record);
  1434. break;
  1435. }
  1436. }
  1437. else
  1438. {
  1439. if (record.Name.Equals(zoneName, StringComparison.OrdinalIgnoreCase) || record.Name.EndsWith("." + zoneName, StringComparison.OrdinalIgnoreCase))
  1440. {
  1441. addedRecords.Add(record);
  1442. }
  1443. else
  1444. {
  1445. switch (record.Type)
  1446. {
  1447. case DnsResourceRecordType.A:
  1448. case DnsResourceRecordType.AAAA:
  1449. addedGlueRecords.Add(record);
  1450. break;
  1451. }
  1452. }
  1453. }
  1454. index++;
  1455. }
  1456. //check sequence soa serial
  1457. DnsSOARecordData deletedSoa = deletedSoaRecord.RDATA as DnsSOARecordData;
  1458. if (currentSoa.Serial != deletedSoa.Serial)
  1459. throw new InvalidOperationException("Current SOA serial does not match with the IXFR difference sequence deleted SOA.");
  1460. //sync difference sequence
  1461. if (deletedRecords.Count > 0)
  1462. {
  1463. foreach (KeyValuePair<string, Dictionary<DnsResourceRecordType, List<DnsResourceRecord>>> deletedEntry in DnsResourceRecord.GroupRecords(deletedRecords))
  1464. {
  1465. AuthZone zone = GetOrAddSubDomainZone(zoneName, deletedEntry.Key);
  1466. if (zone.Name.Equals(zoneName, StringComparison.OrdinalIgnoreCase))
  1467. zone.SyncRecords(deletedEntry.Value, null);
  1468. else if ((zone is SubDomainZone subDomainZone) && subDomainZone.AuthoritativeZone.Name.Equals(zoneName, StringComparison.OrdinalIgnoreCase))
  1469. zone.SyncRecords(deletedEntry.Value, null);
  1470. }
  1471. }
  1472. if (addedRecords.Count > 0)
  1473. {
  1474. foreach (KeyValuePair<string, Dictionary<DnsResourceRecordType, List<DnsResourceRecord>>> addedEntry in DnsResourceRecord.GroupRecords(addedRecords))
  1475. {
  1476. AuthZone zone = GetOrAddSubDomainZone(zoneName, addedEntry.Key);
  1477. if (zone.Name.Equals(zoneName, StringComparison.OrdinalIgnoreCase))
  1478. zone.SyncRecords(null, addedEntry.Value);
  1479. else if ((zone is SubDomainZone subDomainZone) && subDomainZone.AuthoritativeZone.Name.Equals(zoneName, StringComparison.OrdinalIgnoreCase))
  1480. zone.SyncRecords(null, addedEntry.Value);
  1481. }
  1482. }
  1483. if ((deletedGlueRecords.Count > 0) || (addedGlueRecords.Count > 0))
  1484. {
  1485. foreach (AuthZone zone in _root.GetZoneWithSubDomainZones(zoneName))
  1486. zone.SyncGlueRecords(deletedGlueRecords, addedGlueRecords);
  1487. }
  1488. {
  1489. AuthZone zone = GetOrAddSubDomainZone(zoneName, zoneName);
  1490. addedSoaRecord.CopyRecordInfoFrom(currentSoaRecord);
  1491. zone.LoadRecords(DnsResourceRecordType.SOA, new DnsResourceRecord[] { addedSoaRecord });
  1492. }
  1493. //check next difference sequence
  1494. currentSoa = addedSoaRecord.RDATA as DnsSOARecordData;
  1495. deletedRecords.Clear();
  1496. deletedGlueRecords.Clear();
  1497. addedRecords.Clear();
  1498. addedGlueRecords.Clear();
  1499. }
  1500. apexZone.UpdateDnssecStatus();
  1501. //return history
  1502. List<DnsResourceRecord> historyRecords = new List<DnsResourceRecord>(xfrRecords.Count - 2);
  1503. for (int i = 1; i < xfrRecords.Count - 1; i++)
  1504. historyRecords.Add(xfrRecords[i]);
  1505. return historyRecords;
  1506. }
  1507. internal void ImportRecords(string zoneName, IReadOnlyList<DnsResourceRecord> records)
  1508. {
  1509. _ = _root.FindZone(zoneName, out _, out _, out ApexZone apexZone, out _);
  1510. if ((apexZone is null) || !apexZone.Name.Equals(zoneName, StringComparison.OrdinalIgnoreCase))
  1511. throw new DnsServerException("No such zone was found: " + zoneName);
  1512. if ((apexZone is not PrimaryZone) && (apexZone is not ForwarderZone))
  1513. throw new DnsServerException("Zone must be a primary or forwarder type: " + zoneName);
  1514. foreach (KeyValuePair<string, Dictionary<DnsResourceRecordType, List<DnsResourceRecord>>> zoneEntry in DnsResourceRecord.GroupRecords(records))
  1515. {
  1516. if (zoneName.Equals(zoneEntry.Key, StringComparison.OrdinalIgnoreCase))
  1517. {
  1518. foreach (KeyValuePair<DnsResourceRecordType, List<DnsResourceRecord>> rrsetEntry in zoneEntry.Value)
  1519. {
  1520. if (rrsetEntry.Key == DnsResourceRecordType.RRSIG)
  1521. {
  1522. //RRSIG records in response are not complete RRSet
  1523. foreach (DnsResourceRecord record in rrsetEntry.Value)
  1524. apexZone.AddRecord(record);
  1525. }
  1526. else
  1527. {
  1528. apexZone.SetRecords(rrsetEntry.Key, rrsetEntry.Value);
  1529. }
  1530. }
  1531. }
  1532. else
  1533. {
  1534. ValidateZoneNameFor(zoneName, zoneEntry.Key);
  1535. AuthZone authZone = GetOrAddSubDomainZone(zoneName, zoneEntry.Key);
  1536. foreach (KeyValuePair<DnsResourceRecordType, List<DnsResourceRecord>> rrsetEntry in zoneEntry.Value)
  1537. {
  1538. if (rrsetEntry.Key == DnsResourceRecordType.RRSIG)
  1539. {
  1540. //RRSIG records in response are not complete RRSet
  1541. foreach (DnsResourceRecord record in rrsetEntry.Value)
  1542. authZone.AddRecord(record);
  1543. }
  1544. else
  1545. {
  1546. authZone.SetRecords(rrsetEntry.Key, rrsetEntry.Value);
  1547. }
  1548. }
  1549. if (authZone is SubDomainZone subDomainZone)
  1550. subDomainZone.AutoUpdateState();
  1551. }
  1552. }
  1553. apexZone.UpdateDnssecStatus();
  1554. }
  1555. internal void LoadRecords(ApexZone apexZone, IReadOnlyList<DnsResourceRecord> records)
  1556. {
  1557. foreach (KeyValuePair<string, Dictionary<DnsResourceRecordType, List<DnsResourceRecord>>> zoneEntry in DnsResourceRecord.GroupRecords(records))
  1558. {
  1559. if (apexZone.Name.Equals(zoneEntry.Key, StringComparison.OrdinalIgnoreCase))
  1560. {
  1561. foreach (KeyValuePair<DnsResourceRecordType, List<DnsResourceRecord>> rrsetEntry in zoneEntry.Value)
  1562. apexZone.LoadRecords(rrsetEntry.Key, rrsetEntry.Value);
  1563. }
  1564. else
  1565. {
  1566. ValidateZoneNameFor(apexZone.Name, zoneEntry.Key);
  1567. AuthZone authZone = GetOrAddSubDomainZone(apexZone.Name, zoneEntry.Key);
  1568. foreach (KeyValuePair<DnsResourceRecordType, List<DnsResourceRecord>> rrsetEntry in zoneEntry.Value)
  1569. authZone.LoadRecords(rrsetEntry.Key, rrsetEntry.Value);
  1570. if (authZone is SubDomainZone subDomainZone)
  1571. subDomainZone.AutoUpdateState();
  1572. }
  1573. }
  1574. apexZone.UpdateDnssecStatus();
  1575. }
  1576. public void SetRecords(string zoneName, string domain, DnsResourceRecordType type, uint ttl, DnsResourceRecordData[] records)
  1577. {
  1578. ValidateZoneNameFor(zoneName, domain);
  1579. DnsResourceRecord[] resourceRecords = new DnsResourceRecord[records.Length];
  1580. for (int i = 0; i < records.Length; i++)
  1581. resourceRecords[i] = new DnsResourceRecord(domain, type, DnsClass.IN, ttl, records[i]);
  1582. AuthZone authZone = GetOrAddSubDomainZone(zoneName, domain);
  1583. authZone.SetRecords(type, resourceRecords);
  1584. if (authZone is SubDomainZone subDomainZone)
  1585. subDomainZone.AutoUpdateState();
  1586. }
  1587. public void SetRecords(string zoneName, IReadOnlyList<DnsResourceRecord> records)
  1588. {
  1589. for (int i = 1; i < records.Count; i++)
  1590. {
  1591. if (!records[i].Name.Equals(records[0].Name, StringComparison.OrdinalIgnoreCase))
  1592. throw new InvalidOperationException();
  1593. if (records[i].Type != records[0].Type)
  1594. throw new InvalidOperationException();
  1595. if (records[i].Class != records[0].Class)
  1596. throw new InvalidOperationException();
  1597. }
  1598. AuthZone authZone = GetOrAddSubDomainZone(zoneName, records[0].Name);
  1599. authZone.SetRecords(records[0].Type, records);
  1600. if (authZone is SubDomainZone subDomainZone)
  1601. subDomainZone.AutoUpdateState();
  1602. }
  1603. public void SetRecord(string zoneName, DnsResourceRecord record)
  1604. {
  1605. ValidateZoneNameFor(zoneName, record.Name);
  1606. AuthZone authZone = GetOrAddSubDomainZone(zoneName, record.Name);
  1607. authZone.SetRecords(record.Type, new DnsResourceRecord[] { record });
  1608. if (authZone is SubDomainZone subDomainZone)
  1609. subDomainZone.AutoUpdateState();
  1610. }
  1611. public void AddRecord(string zoneName, string domain, DnsResourceRecordType type, uint ttl, DnsResourceRecordData record)
  1612. {
  1613. ValidateZoneNameFor(zoneName, domain);
  1614. AuthZone authZone = GetOrAddSubDomainZone(zoneName, domain);
  1615. authZone.AddRecord(new DnsResourceRecord(authZone.Name, type, DnsClass.IN, ttl, record));
  1616. if (authZone is SubDomainZone subDomainZone)
  1617. subDomainZone.AutoUpdateState();
  1618. }
  1619. public void AddRecord(string zoneName, DnsResourceRecord record)
  1620. {
  1621. ValidateZoneNameFor(zoneName, record.Name);
  1622. AuthZone authZone = GetOrAddSubDomainZone(zoneName, record.Name);
  1623. authZone.AddRecord(record);
  1624. if (authZone is SubDomainZone subDomainZone)
  1625. subDomainZone.AutoUpdateState();
  1626. }
  1627. public void UpdateRecord(string zoneName, DnsResourceRecord oldRecord, DnsResourceRecord newRecord)
  1628. {
  1629. ValidateZoneNameFor(zoneName, oldRecord.Name);
  1630. ValidateZoneNameFor(zoneName, newRecord.Name);
  1631. if (oldRecord.Type != newRecord.Type)
  1632. throw new DnsServerException("Cannot update record: new record must be of same type.");
  1633. if (oldRecord.Type == DnsResourceRecordType.SOA)
  1634. throw new DnsServerException("Cannot update record: use SetRecords() for updating SOA record.");
  1635. if (!_root.TryGet(zoneName, oldRecord.Name, out AuthZone authZone))
  1636. throw new DnsServerException("Cannot update record: zone does not exists.");
  1637. switch (oldRecord.Type)
  1638. {
  1639. case DnsResourceRecordType.CNAME:
  1640. case DnsResourceRecordType.DNAME:
  1641. case DnsResourceRecordType.APP:
  1642. if (oldRecord.Name.Equals(newRecord.Name, StringComparison.OrdinalIgnoreCase))
  1643. {
  1644. authZone.SetRecords(newRecord.Type, new DnsResourceRecord[] { newRecord });
  1645. if (authZone is SubDomainZone subDomainZone)
  1646. subDomainZone.AutoUpdateState();
  1647. }
  1648. else
  1649. {
  1650. authZone.DeleteRecords(oldRecord.Type);
  1651. if (authZone is SubDomainZone subDomainZone)
  1652. {
  1653. if (authZone.IsEmpty)
  1654. _root.TryRemove(oldRecord.Name, out SubDomainZone _); //remove empty sub zone
  1655. else
  1656. subDomainZone.AutoUpdateState();
  1657. }
  1658. AuthZone newZone = GetOrAddSubDomainZone(zoneName, newRecord.Name);
  1659. newZone.SetRecords(newRecord.Type, new DnsResourceRecord[] { newRecord });
  1660. if (newZone is SubDomainZone subDomainZone1)
  1661. subDomainZone1.AutoUpdateState();
  1662. }
  1663. break;
  1664. default:
  1665. if (oldRecord.Name.Equals(newRecord.Name, StringComparison.OrdinalIgnoreCase))
  1666. {
  1667. authZone.UpdateRecord(oldRecord, newRecord);
  1668. if (authZone is SubDomainZone subDomainZone)
  1669. subDomainZone.AutoUpdateState();
  1670. }
  1671. else
  1672. {
  1673. if (!authZone.DeleteRecord(oldRecord.Type, oldRecord.RDATA))
  1674. throw new DnsWebServiceException("Cannot update record: the old record does not exists.");
  1675. if (authZone is SubDomainZone subDomainZone)
  1676. {
  1677. if (authZone.IsEmpty)
  1678. _root.TryRemove(oldRecord.Name, out SubDomainZone _); //remove empty sub zone
  1679. else
  1680. subDomainZone.AutoUpdateState();
  1681. }
  1682. AuthZone newZone = GetOrAddSubDomainZone(zoneName, newRecord.Name);
  1683. newZone.AddRecord(newRecord);
  1684. if (newZone is SubDomainZone subDomainZone1)
  1685. subDomainZone1.AutoUpdateState();
  1686. }
  1687. break;
  1688. }
  1689. }
  1690. public bool DeleteRecord(string zoneName, string domain, DnsResourceRecordType type, DnsResourceRecordData record)
  1691. {
  1692. ValidateZoneNameFor(zoneName, domain);
  1693. if (_root.TryGet(zoneName, domain, out AuthZone authZone))
  1694. {
  1695. if (authZone.DeleteRecord(type, record))
  1696. {
  1697. if (authZone is SubDomainZone subDomainZone)
  1698. {
  1699. if (authZone.IsEmpty)
  1700. _root.TryRemove(domain, out SubDomainZone _); //remove empty sub zone
  1701. else
  1702. subDomainZone.AutoUpdateState();
  1703. }
  1704. return true;
  1705. }
  1706. }
  1707. return false;
  1708. }
  1709. public bool DeleteRecords(string zoneName, string domain, DnsResourceRecordType type)
  1710. {
  1711. ValidateZoneNameFor(zoneName, domain);
  1712. if (_root.TryGet(zoneName, domain, out AuthZone authZone))
  1713. {
  1714. if (authZone.DeleteRecords(type))
  1715. {
  1716. if (authZone is SubDomainZone subDomainZone)
  1717. {
  1718. if (authZone.IsEmpty)
  1719. _root.TryRemove(domain, out SubDomainZone _); //remove empty sub zone
  1720. else
  1721. subDomainZone.AutoUpdateState();
  1722. }
  1723. return true;
  1724. }
  1725. }
  1726. return false;
  1727. }
  1728. public IReadOnlyList<AuthZoneInfo> GetAllZones()
  1729. {
  1730. _zoneIndexLock.EnterReadLock();
  1731. try
  1732. {
  1733. return new List<AuthZoneInfo>(_zoneIndex);
  1734. }
  1735. finally
  1736. {
  1737. _zoneIndexLock.ExitReadLock();
  1738. }
  1739. }
  1740. public ZonesPage GetZonesPage(int pageNumber, int zonesPerPage)
  1741. {
  1742. _zoneIndexLock.EnterReadLock();
  1743. try
  1744. {
  1745. int totalZones = _zoneIndex.Count;
  1746. if (totalZones < 1)
  1747. return new ZonesPage(0, 0, 0, Array.Empty<AuthZoneInfo>());
  1748. if (pageNumber == 0)
  1749. pageNumber = 1;
  1750. int totalPages = (totalZones / zonesPerPage) + (totalZones % zonesPerPage > 0 ? 1 : 0);
  1751. if ((pageNumber > totalPages) || (pageNumber < 0))
  1752. pageNumber = totalPages;
  1753. int start = (pageNumber - 1) * zonesPerPage;
  1754. int end = Math.Min(start + zonesPerPage, totalZones);
  1755. List<AuthZoneInfo> zones = new List<AuthZoneInfo>(end - start);
  1756. for (int i = start; i < end; i++)
  1757. zones.Add(_zoneIndex[i]);
  1758. return new ZonesPage(pageNumber, totalPages, totalZones, zones);
  1759. }
  1760. finally
  1761. {
  1762. _zoneIndexLock.ExitReadLock();
  1763. }
  1764. }
  1765. public void ListSubDomains(string domain, List<string> subDomains)
  1766. {
  1767. _root.ListSubDomains(domain, subDomains);
  1768. }
  1769. public DnsDatagram QueryClosestDelegation(DnsDatagram request)
  1770. {
  1771. _ = _root.FindZone(request.Question[0].Name, out _, out SubDomainZone delegation, out ApexZone apexZone, out _);
  1772. if (delegation is not null)
  1773. {
  1774. bool dnssecOk = request.DnssecOk && (apexZone.DnssecStatus != AuthZoneDnssecStatus.Unsigned);
  1775. return GetReferralResponse(request, dnssecOk, delegation, apexZone);
  1776. }
  1777. //no delegation found
  1778. return null;
  1779. }
  1780. public DnsDatagram Query(DnsDatagram request, bool isRecursionAllowed)
  1781. {
  1782. DnsQuestionRecord question = request.Question[0];
  1783. AuthZone zone = _root.FindZone(question.Name, out SubDomainZone closest, out SubDomainZone delegation, out ApexZone apexZone, out bool hasSubDomains);
  1784. if ((apexZone is null) || !apexZone.IsActive)
  1785. return null; //no authority for requested zone
  1786. bool dnssecOk = request.DnssecOk && (apexZone.DnssecStatus != AuthZoneDnssecStatus.Unsigned);
  1787. if ((zone is null) || !zone.IsActive)
  1788. {
  1789. //zone not found
  1790. if ((delegation is not null) && delegation.IsActive && (delegation.Name.Length > apexZone.Name.Length))
  1791. return GetReferralResponse(request, dnssecOk, delegation, apexZone);
  1792. if (apexZone is StubZone)
  1793. return GetReferralResponse(request, false, apexZone, apexZone);
  1794. DnsResponseCode rCode = DnsResponseCode.NoError;
  1795. IReadOnlyList<DnsResourceRecord> answer = null;
  1796. IReadOnlyList<DnsResourceRecord> authority = null;
  1797. if (closest is not null)
  1798. {
  1799. answer = closest.QueryRecords(DnsResourceRecordType.DNAME, dnssecOk);
  1800. if ((answer.Count > 0) && (answer[0].Type == DnsResourceRecordType.DNAME))
  1801. {
  1802. if (!DoDNAMESubstitution(question, dnssecOk, answer, out answer))
  1803. rCode = DnsResponseCode.YXDomain;
  1804. }
  1805. else
  1806. {
  1807. answer = null;
  1808. authority = closest.QueryRecords(DnsResourceRecordType.APP, false);
  1809. }
  1810. }
  1811. if (((answer is null) || (answer.Count == 0)) && ((authority is null) || (authority.Count == 0)))
  1812. {
  1813. answer = apexZone.QueryRecords(DnsResourceRecordType.DNAME, dnssecOk);
  1814. if ((answer.Count > 0) && (answer[0].Type == DnsResourceRecordType.DNAME))
  1815. {
  1816. if (!DoDNAMESubstitution(question, dnssecOk, answer, out answer))
  1817. rCode = DnsResponseCode.YXDomain;
  1818. }
  1819. else
  1820. {
  1821. answer = null;
  1822. authority = apexZone.QueryRecords(DnsResourceRecordType.APP, false);
  1823. if (authority.Count == 0)
  1824. {
  1825. if (apexZone is ForwarderZone)
  1826. return GetForwarderResponse(request, null, closest, apexZone); //no DNAME or APP record available so process FWD response
  1827. if (!hasSubDomains)
  1828. rCode = DnsResponseCode.NxDomain;
  1829. authority = apexZone.QueryRecords(DnsResourceRecordType.SOA, dnssecOk);
  1830. if (dnssecOk)
  1831. {
  1832. //add proof of non existence (NXDOMAIN) to prove the qname does not exists
  1833. IReadOnlyList<DnsResourceRecord> nsecRecords;
  1834. if (apexZone.DnssecStatus == AuthZoneDnssecStatus.SignedWithNSEC3)
  1835. nsecRecords = _root.FindNSec3ProofOfNonExistenceNxDomain(question.Name, false);
  1836. else
  1837. nsecRecords = _root.FindNSecProofOfNonExistenceNxDomain(question.Name, false);
  1838. if (nsecRecords.Count > 0)
  1839. {
  1840. List<DnsResourceRecord> newAuthority = new List<DnsResourceRecord>(authority.Count + nsecRecords.Count);
  1841. newAuthority.AddRange(authority);
  1842. newAuthority.AddRange(nsecRecords);
  1843. authority = newAuthority;
  1844. }
  1845. }
  1846. }
  1847. }
  1848. }
  1849. return new DnsDatagram(request.Identifier, true, DnsOpcode.StandardQuery, true, false, request.RecursionDesired, isRecursionAllowed, false, false, rCode, request.Question, answer, authority);
  1850. }
  1851. else
  1852. {
  1853. //zone found
  1854. if (question.Type == DnsResourceRecordType.DS)
  1855. {
  1856. if (zone is ApexZone)
  1857. {
  1858. if (delegation is null || !delegation.IsActive || (delegation.Name.Length > apexZone.Name.Length))
  1859. return null; //no authoritative parent side delegation zone available to answer for DS
  1860. zone = delegation; //switch zone to parent side sub domain delegation zone for DS record
  1861. }
  1862. }
  1863. else if (zone.Equals(delegation))
  1864. {
  1865. //zone is delegation
  1866. return GetReferralResponse(request, dnssecOk, delegation, apexZone);
  1867. }
  1868. IReadOnlyList<DnsResourceRecord> authority = null;
  1869. IReadOnlyList<DnsResourceRecord> additional;
  1870. IReadOnlyList<DnsResourceRecord> answers = zone.QueryRecords(question.Type, dnssecOk);
  1871. if (answers.Count == 0)
  1872. {
  1873. //record type not found
  1874. if (question.Type == DnsResourceRecordType.DS)
  1875. {
  1876. //check for correct auth zone
  1877. if (apexZone.Name.Equals(question.Name, StringComparison.OrdinalIgnoreCase))
  1878. {
  1879. //current auth zone is child side; find parent side auth zone for DS
  1880. string parentZone = GetParentZone(question.Name);
  1881. if (parentZone is null)
  1882. parentZone = string.Empty;
  1883. _ = _root.FindZone(parentZone, out _, out _, out apexZone, out _);
  1884. if ((apexZone is null) || !apexZone.IsActive)
  1885. return null; //no authority for requested zone
  1886. }
  1887. }
  1888. else
  1889. {
  1890. //check for delegation, stub & forwarder
  1891. if ((delegation is not null) && delegation.IsActive && (delegation.Name.Length > apexZone.Name.Length))
  1892. return GetReferralResponse(request, dnssecOk, delegation, apexZone);
  1893. if (apexZone is StubZone)
  1894. return GetReferralResponse(request, false, apexZone, apexZone);
  1895. }
  1896. authority = zone.QueryRecords(DnsResourceRecordType.APP, false);
  1897. if (authority.Count == 0)
  1898. {
  1899. if (apexZone is ForwarderZone)
  1900. return GetForwarderResponse(request, zone, closest, apexZone); //no APP record available so process FWD response
  1901. authority = apexZone.QueryRecords(DnsResourceRecordType.SOA, dnssecOk);
  1902. if (dnssecOk)
  1903. {
  1904. //add proof of non existence (NODATA) to prove that no such type or record exists
  1905. IReadOnlyList<DnsResourceRecord> nsecRecords;
  1906. if (apexZone.DnssecStatus == AuthZoneDnssecStatus.SignedWithNSEC3)
  1907. nsecRecords = _root.FindNSec3ProofOfNonExistenceNoData(zone, apexZone);
  1908. else
  1909. nsecRecords = _root.FindNSecProofOfNonExistenceNoData(zone);
  1910. if (nsecRecords.Count > 0)
  1911. {
  1912. List<DnsResourceRecord> newAuthority = new List<DnsResourceRecord>(authority.Count + nsecRecords.Count);
  1913. newAuthority.AddRange(authority);
  1914. newAuthority.AddRange(nsecRecords);
  1915. authority = newAuthority;
  1916. }
  1917. }
  1918. }
  1919. additional = null;
  1920. }
  1921. else
  1922. {
  1923. //record type found
  1924. if (zone.Name.Contains('*') && !zone.Name.Equals(question.Name, StringComparison.OrdinalIgnoreCase))
  1925. {
  1926. //wildcard zone; generate new answer records
  1927. DnsResourceRecord[] wildcardAnswers = new DnsResourceRecord[answers.Count];
  1928. for (int i = 0; i < answers.Count; i++)
  1929. wildcardAnswers[i] = new DnsResourceRecord(question.Name, answers[i].Type, answers[i].Class, answers[i].TTL, answers[i].RDATA) { Tag = answers[i].Tag };
  1930. answers = wildcardAnswers;
  1931. //add proof of non existence (WILDCARD) to prove that the wildcard expansion was legit and the qname actually does not exists
  1932. if (dnssecOk)
  1933. {
  1934. IReadOnlyList<DnsResourceRecord> nsecRecords;
  1935. if (apexZone.DnssecStatus == AuthZoneDnssecStatus.SignedWithNSEC3)
  1936. nsecRecords = _root.FindNSec3ProofOfNonExistenceNxDomain(question.Name, true);
  1937. else
  1938. nsecRecords = _root.FindNSecProofOfNonExistenceNxDomain(question.Name, true);
  1939. if (nsecRecords.Count > 0)
  1940. authority = nsecRecords;
  1941. }
  1942. }
  1943. DnsResourceRecord lastRR = answers[answers.Count - 1];
  1944. if ((lastRR.Type != question.Type) && (question.Type != DnsResourceRecordType.ANY))
  1945. {
  1946. switch (lastRR.Type)
  1947. {
  1948. case DnsResourceRecordType.CNAME:
  1949. List<DnsResourceRecord> newAnswers = new List<DnsResourceRecord>(answers.Count + 1);
  1950. newAnswers.AddRange(answers);
  1951. ResolveCNAME(question, dnssecOk, lastRR, newAnswers);
  1952. answers = newAnswers;
  1953. break;
  1954. case DnsResourceRecordType.ANAME:
  1955. authority = apexZone.GetRecords(DnsResourceRecordType.SOA); //adding SOA for use with NO DATA response
  1956. break;
  1957. }
  1958. }
  1959. switch (question.Type)
  1960. {
  1961. case DnsResourceRecordType.NS:
  1962. case DnsResourceRecordType.MX:
  1963. case DnsResourceRecordType.SRV:
  1964. case DnsResourceRecordType.SVCB:
  1965. case DnsResourceRecordType.HTTPS:
  1966. additional = GetAdditionalRecords(answers, dnssecOk);
  1967. break;
  1968. default:
  1969. additional = null;
  1970. break;
  1971. }
  1972. }
  1973. return new DnsDatagram(request.Identifier, true, DnsOpcode.StandardQuery, true, false, request.RecursionDesired, isRecursionAllowed, false, false, DnsResponseCode.NoError, request.Question, answers, authority, additional);
  1974. }
  1975. }
  1976. public void LoadTrustAnchorsTo(DnsClient dnsClient, string domain, DnsResourceRecordType type)
  1977. {
  1978. if (type == DnsResourceRecordType.DS)
  1979. {
  1980. domain = GetParentZone(domain);
  1981. if (domain is null)
  1982. domain = "";
  1983. }
  1984. AuthZoneInfo zoneInfo = _dnsServer.AuthZoneManager.FindAuthZoneInfo(domain, false);
  1985. if ((zoneInfo is not null) && (zoneInfo.DnssecStatus != AuthZoneDnssecStatus.Unsigned))
  1986. {
  1987. IReadOnlyList<DnsResourceRecord> dnsKeyRecords = zoneInfo.GetApexRecords(DnsResourceRecordType.DNSKEY);
  1988. foreach (DnsResourceRecord dnsKeyRecord in dnsKeyRecords)
  1989. {
  1990. DnsDNSKEYRecordData dnsKey = dnsKeyRecord.RDATA as DnsDNSKEYRecordData;
  1991. if (dnsKey.Flags.HasFlag(DnsDnsKeyFlag.SecureEntryPoint) && !dnsKey.Flags.HasFlag(DnsDnsKeyFlag.Revoke))
  1992. {
  1993. DnsDSRecordData dsRecord = dnsKey.CreateDS(dnsKeyRecord.Name, DnssecDigestType.SHA256);
  1994. dnsClient.AddTrustAnchor(zoneInfo.Name, dsRecord);
  1995. }
  1996. }
  1997. }
  1998. }
  1999. public AuthZoneInfo LoadZoneFrom(Stream s)
  2000. {
  2001. BinaryReader bR = new BinaryReader(s);
  2002. if (Encoding.ASCII.GetString(bR.ReadBytes(2)) != "DZ")
  2003. throw new InvalidDataException("DnsServer zone file format is invalid.");
  2004. switch (bR.ReadByte())
  2005. {
  2006. case 2:
  2007. {
  2008. DnsResourceRecord[] records = new DnsResourceRecord[bR.ReadInt32()];
  2009. if (records.Length == 0)
  2010. throw new InvalidDataException("Zone does not contain SOA record.");
  2011. DnsResourceRecord soaRecord = null;
  2012. for (int i = 0; i < records.Length; i++)
  2013. {
  2014. records[i] = new DnsResourceRecord(s);
  2015. if (records[i].Type == DnsResourceRecordType.SOA)
  2016. soaRecord = records[i];
  2017. }
  2018. if (soaRecord == null)
  2019. throw new InvalidDataException("Zone does not contain SOA record.");
  2020. //make zone info
  2021. AuthZoneType zoneType;
  2022. if (_dnsServer.ServerDomain.Equals((soaRecord.RDATA as DnsSOARecordData).PrimaryNameServer, StringComparison.OrdinalIgnoreCase))
  2023. zoneType = AuthZoneType.Primary;
  2024. else
  2025. zoneType = AuthZoneType.Stub;
  2026. AuthZoneInfo zoneInfo = new AuthZoneInfo(records[0].Name, zoneType, false);
  2027. //create zone
  2028. ApexZone apexZone = CreateEmptyZone(zoneInfo);
  2029. try
  2030. {
  2031. //load records
  2032. LoadRecords(apexZone, records);
  2033. }
  2034. catch
  2035. {
  2036. DeleteZone(zoneInfo.Name);
  2037. throw;
  2038. }
  2039. //init zone
  2040. switch (zoneInfo.Type)
  2041. {
  2042. case AuthZoneType.Primary:
  2043. (apexZone as PrimaryZone).TriggerNotify();
  2044. break;
  2045. }
  2046. return new AuthZoneInfo(apexZone);
  2047. }
  2048. case 3:
  2049. {
  2050. bool zoneDisabled = bR.ReadBoolean();
  2051. DnsResourceRecord[] records = new DnsResourceRecord[bR.ReadInt32()];
  2052. if (records.Length == 0)
  2053. throw new InvalidDataException("Zone does not contain SOA record.");
  2054. DnsResourceRecord soaRecord = null;
  2055. for (int i = 0; i < records.Length; i++)
  2056. {
  2057. records[i] = new DnsResourceRecord(s);
  2058. records[i].Tag = new AuthRecordInfo(bR, records[i].Type == DnsResourceRecordType.SOA);
  2059. if (records[i].Type == DnsResourceRecordType.SOA)
  2060. soaRecord = records[i];
  2061. }
  2062. if (soaRecord == null)
  2063. throw new InvalidDataException("Zone does not contain SOA record.");
  2064. //make zone info
  2065. AuthZoneType zoneType;
  2066. if (_dnsServer.ServerDomain.Equals((soaRecord.RDATA as DnsSOARecordData).PrimaryNameServer, StringComparison.OrdinalIgnoreCase))
  2067. zoneType = AuthZoneType.Primary;
  2068. else
  2069. zoneType = AuthZoneType.Stub;
  2070. AuthZoneInfo zoneInfo = new AuthZoneInfo(records[0].Name, zoneType, zoneDisabled);
  2071. //create zone
  2072. ApexZone apexZone = CreateEmptyZone(zoneInfo);
  2073. try
  2074. {
  2075. //load records
  2076. LoadRecords(apexZone, records);
  2077. }
  2078. catch
  2079. {
  2080. DeleteZone(zoneInfo.Name);
  2081. throw;
  2082. }
  2083. //init zone
  2084. switch (zoneInfo.Type)
  2085. {
  2086. case AuthZoneType.Primary:
  2087. (apexZone as PrimaryZone).TriggerNotify();
  2088. break;
  2089. }
  2090. return new AuthZoneInfo(apexZone);
  2091. }
  2092. case 4:
  2093. {
  2094. //read zone info
  2095. AuthZoneInfo zoneInfo = new AuthZoneInfo(bR);
  2096. //create zone
  2097. ApexZone apexZone = CreateEmptyZone(zoneInfo);
  2098. //read all zone records
  2099. DnsResourceRecord[] records = new DnsResourceRecord[bR.ReadInt32()];
  2100. if (records.Length > 0)
  2101. {
  2102. for (int i = 0; i < records.Length; i++)
  2103. {
  2104. records[i] = new DnsResourceRecord(s);
  2105. records[i].Tag = new AuthRecordInfo(bR, records[i].Type == DnsResourceRecordType.SOA);
  2106. }
  2107. try
  2108. {
  2109. //load records
  2110. LoadRecords(apexZone, records);
  2111. }
  2112. catch
  2113. {
  2114. DeleteZone(zoneInfo.Name);
  2115. throw;
  2116. }
  2117. //init zone
  2118. switch (zoneInfo.Type)
  2119. {
  2120. case AuthZoneType.Primary:
  2121. (apexZone as PrimaryZone).TriggerNotify();
  2122. break;
  2123. case AuthZoneType.Secondary:
  2124. SecondaryZone secondary = apexZone as SecondaryZone;
  2125. secondary.TriggerNotify();
  2126. secondary.TriggerRefresh();
  2127. break;
  2128. case AuthZoneType.Stub:
  2129. (apexZone as StubZone).TriggerRefresh();
  2130. break;
  2131. }
  2132. }
  2133. return new AuthZoneInfo(apexZone);
  2134. }
  2135. default:
  2136. throw new InvalidDataException("DNS Zone file version not supported.");
  2137. }
  2138. }
  2139. public void WriteZoneTo(string zoneName, Stream s)
  2140. {
  2141. AuthZoneInfo zoneInfo = GetAuthZoneInfo(zoneName, true);
  2142. if (zoneInfo is null)
  2143. throw new InvalidOperationException("Zone was not found: " + zoneName);
  2144. //serialize zone
  2145. BinaryWriter bW = new BinaryWriter(s);
  2146. bW.Write(Encoding.ASCII.GetBytes("DZ")); //format
  2147. bW.Write((byte)4); //version
  2148. //write zone info
  2149. if (zoneInfo.Internal)
  2150. throw new InvalidOperationException("Cannot save zones marked as internal.");
  2151. zoneInfo.WriteTo(bW);
  2152. //write all zone records
  2153. List<DnsResourceRecord> records = new List<DnsResourceRecord>();
  2154. ListAllZoneRecords(zoneName, records);
  2155. bW.Write(records.Count);
  2156. foreach (DnsResourceRecord record in records)
  2157. {
  2158. record.WriteTo(s);
  2159. if (record.Tag is not AuthRecordInfo rrInfo)
  2160. rrInfo = AuthRecordInfo.Default; //default info
  2161. rrInfo.WriteTo(bW);
  2162. }
  2163. }
  2164. public void SaveZoneFile(string zoneName)
  2165. {
  2166. zoneName = zoneName.ToLower();
  2167. using (MemoryStream mS = new MemoryStream())
  2168. {
  2169. //serialize zone
  2170. WriteZoneTo(zoneName, mS);
  2171. //write to zone file
  2172. mS.Position = 0;
  2173. using (FileStream fS = new FileStream(Path.Combine(_dnsServer.ConfigFolder, "zones", zoneName + ".zone"), FileMode.Create, FileAccess.Write))
  2174. {
  2175. mS.CopyTo(fS);
  2176. }
  2177. }
  2178. _dnsServer.LogManager?.Write("Saved zone file for domain: " + (zoneName == "" ? "<root>" : zoneName));
  2179. }
  2180. public void DeleteZoneFile(string zoneName)
  2181. {
  2182. zoneName = zoneName.ToLower();
  2183. File.Delete(Path.Combine(_dnsServer.ConfigFolder, "zones", zoneName + ".zone"));
  2184. _dnsServer.LogManager?.Write("Deleted zone file for domain: " + zoneName);
  2185. }
  2186. #endregion
  2187. #region properties
  2188. public string ServerDomain
  2189. {
  2190. get { return _serverDomain; }
  2191. set { UpdateServerDomain(value); }
  2192. }
  2193. public int TotalZones
  2194. { get { return _zoneIndex.Count; } }
  2195. #endregion
  2196. public class ZonesPage
  2197. {
  2198. #region variables
  2199. readonly long _pageNumber;
  2200. readonly long _totalPages;
  2201. readonly long _totalZones;
  2202. readonly IReadOnlyList<AuthZoneInfo> _zones;
  2203. #endregion
  2204. #region constructor
  2205. public ZonesPage(long pageNumber, long totalPages, long totalZones, IReadOnlyList<AuthZoneInfo> zones)
  2206. {
  2207. _pageNumber = pageNumber;
  2208. _totalPages = totalPages;
  2209. _totalZones = totalZones;
  2210. _zones = zones;
  2211. }
  2212. #endregion
  2213. #region properties
  2214. public long PageNumber
  2215. { get { return _pageNumber; } }
  2216. public long TotalPages
  2217. { get { return _totalPages; } }
  2218. public long TotalZones
  2219. { get { return _totalZones; } }
  2220. public IReadOnlyList<AuthZoneInfo> Zones
  2221. { get { return _zones; } }
  2222. #endregion
  2223. }
  2224. }
  2225. }