App.cs 29 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813
  1. /*
  2. Technitium DNS Server
  3. Copyright (C) 2024 Shreyas Zare (shreyas@technitium.com)
  4. This program is free software: you can redistribute it and/or modify
  5. it under the terms of the GNU General Public License as published by
  6. the Free Software Foundation, either version 3 of the License, or
  7. (at your option) any later version.
  8. This program is distributed in the hope that it will be useful,
  9. but WITHOUT ANY WARRANTY; without even the implied warranty of
  10. MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  11. GNU General Public License for more details.
  12. You should have received a copy of the GNU General Public License
  13. along with this program. If not, see <http://www.gnu.org/licenses/>.
  14. */
  15. using DnsServerCore.ApplicationCommon;
  16. using System;
  17. using System.Collections.Generic;
  18. using System.Globalization;
  19. using System.IO;
  20. using System.Net;
  21. using System.Net.Sockets;
  22. using System.Text.Json;
  23. using System.Threading;
  24. using System.Threading.Tasks;
  25. using TechnitiumLibrary;
  26. using TechnitiumLibrary.Net;
  27. using TechnitiumLibrary.Net.Dns;
  28. using TechnitiumLibrary.Net.Dns.ResourceRecords;
  29. namespace DnsBlockList
  30. {
  31. //DNS Blacklists and Whitelists
  32. //https://www.rfc-editor.org/rfc/rfc5782
  33. public sealed class App : IDnsApplication, IDnsAppRecordRequestHandler
  34. {
  35. #region variables
  36. IDnsServer _dnsServer;
  37. Dictionary<string, BlockList> _dnsBlockLists;
  38. #endregion
  39. #region IDisposable
  40. public void Dispose()
  41. {
  42. if (_dnsBlockLists is not null)
  43. {
  44. foreach (KeyValuePair<string, BlockList> dnsBlockList in _dnsBlockLists)
  45. dnsBlockList.Value.Dispose();
  46. _dnsBlockLists = null;
  47. }
  48. }
  49. #endregion
  50. #region private
  51. private static bool TryParseDnsblDomain(string qName, string appRecordName, out IPAddress address, out string domain)
  52. {
  53. qName = qName.Substring(0, qName.Length - appRecordName.Length - 1);
  54. string[] parts = qName.Split('.');
  55. string lastPart = parts[parts.Length - 1];
  56. if (byte.TryParse(lastPart, out _) || byte.TryParse(lastPart, NumberStyles.HexNumber, null, out _))
  57. {
  58. switch (parts.Length)
  59. {
  60. case 4:
  61. {
  62. Span<byte> buffer = stackalloc byte[4];
  63. for (int i = 0, j = parts.Length - 1; (i < 4) && (j > -1); i++, j--)
  64. buffer[i] = byte.Parse(parts[j]);
  65. address = new IPAddress(buffer);
  66. domain = null;
  67. return true;
  68. }
  69. case 32:
  70. {
  71. Span<byte> buffer = stackalloc byte[16];
  72. for (int i = 0, j = parts.Length - 1; (i < 16) && (j > 0); i++, j -= 2)
  73. buffer[i] = (byte)(byte.Parse(parts[j], NumberStyles.HexNumber) << 4 | byte.Parse(parts[j - 1], NumberStyles.HexNumber));
  74. address = new IPAddress(buffer);
  75. domain = null;
  76. return true;
  77. }
  78. default:
  79. address = null;
  80. domain = null;
  81. return false;
  82. }
  83. }
  84. else
  85. {
  86. address = null;
  87. domain = lastPart;
  88. for (int i = parts.Length - 2; i > -1; i--)
  89. domain = parts[i] + "." + domain;
  90. return true;
  91. }
  92. }
  93. private Tuple<string, BlockList> ReadBlockList(JsonElement jsonBlockList)
  94. {
  95. BlockList blockList;
  96. string name = jsonBlockList.GetProperty("name").GetString();
  97. BlockListType type = jsonBlockList.GetPropertyEnumValue("type", BlockListType.Ip);
  98. if ((_dnsBlockLists is not null) && _dnsBlockLists.TryGetValue(name, out BlockList existingBlockList) && (existingBlockList.Type == type))
  99. {
  100. existingBlockList.ReloadConfig(jsonBlockList);
  101. blockList = existingBlockList;
  102. }
  103. else
  104. {
  105. switch (type)
  106. {
  107. case BlockListType.Ip:
  108. blockList = new IpBlockList(_dnsServer, jsonBlockList);
  109. break;
  110. case BlockListType.Domain:
  111. blockList = new DomainBlockList(_dnsServer, jsonBlockList);
  112. break;
  113. default:
  114. throw new NotSupportedException("DNSBL block list type is not supported: " + type.ToString());
  115. }
  116. }
  117. return new Tuple<string, BlockList>(blockList.Name, blockList);
  118. }
  119. #endregion
  120. #region public
  121. public Task InitializeAsync(IDnsServer dnsServer, string config)
  122. {
  123. _dnsServer = dnsServer;
  124. using JsonDocument jsonDocument = JsonDocument.Parse(config);
  125. JsonElement jsonConfig = jsonDocument.RootElement;
  126. if (jsonConfig.TryReadArrayAsMap("dnsBlockLists", ReadBlockList, out Dictionary<string, BlockList> dnsBlockLists))
  127. {
  128. if (_dnsBlockLists is not null)
  129. {
  130. foreach (KeyValuePair<string, BlockList> dnsBlockList in _dnsBlockLists)
  131. {
  132. if (!dnsBlockLists.ContainsKey(dnsBlockList.Key))
  133. dnsBlockList.Value.Dispose();
  134. }
  135. }
  136. _dnsBlockLists = dnsBlockLists;
  137. }
  138. else
  139. {
  140. if (_dnsBlockLists is not null)
  141. {
  142. foreach (KeyValuePair<string, BlockList> dnsBlockList in _dnsBlockLists)
  143. dnsBlockList.Value.Dispose();
  144. }
  145. _dnsBlockLists = null;
  146. }
  147. return Task.CompletedTask;
  148. }
  149. public async Task<DnsDatagram> ProcessRequestAsync(DnsDatagram request, IPEndPoint remoteEP, DnsTransportProtocol protocol, bool isRecursionAllowed, string zoneName, string appRecordName, uint appRecordTtl, string appRecordData)
  150. {
  151. DnsQuestionRecord question = request.Question[0];
  152. string qname = question.Name;
  153. if (qname.Length == appRecordName.Length)
  154. return null;
  155. if ((_dnsBlockLists is null) || !TryParseDnsblDomain(qname, appRecordName, out IPAddress address, out string domain))
  156. return null;
  157. using JsonDocument jsonDocument = JsonDocument.Parse(appRecordData);
  158. JsonElement jsonAppRecordData = jsonDocument.RootElement;
  159. if (jsonAppRecordData.TryReadArray("dnsBlockLists", out string[] dnsBlockLists))
  160. {
  161. bool isBlocked = false;
  162. IPAddress responseA = null;
  163. string responseTXT = null;
  164. if (address is not null)
  165. {
  166. foreach (string dnsBlockList in dnsBlockLists)
  167. {
  168. if (_dnsBlockLists.TryGetValue(dnsBlockList, out BlockList blockList) && blockList.Enabled && (blockList.Type == BlockListType.Ip) && blockList.IsBlocked(address, out responseA, out responseTXT))
  169. {
  170. isBlocked = true;
  171. if (!string.IsNullOrEmpty(responseTXT))
  172. responseTXT = responseTXT.Replace("{ip}", address.ToString());
  173. break;
  174. }
  175. }
  176. }
  177. else if (domain is not null)
  178. {
  179. foreach (string dnsBlockList in dnsBlockLists)
  180. {
  181. if (_dnsBlockLists.TryGetValue(dnsBlockList, out BlockList blockList) && blockList.Enabled && (blockList.Type == BlockListType.Domain) && blockList.IsBlocked(domain, out string foundDomain, out responseA, out responseTXT))
  182. {
  183. isBlocked = true;
  184. if (!string.IsNullOrEmpty(responseTXT))
  185. responseTXT = responseTXT.Replace("{domain}", foundDomain);
  186. break;
  187. }
  188. }
  189. }
  190. if (isBlocked)
  191. {
  192. switch (question.Type)
  193. {
  194. case DnsResourceRecordType.A:
  195. return new DnsDatagram(request.Identifier, true, DnsOpcode.StandardQuery, true, false, request.RecursionDesired, isRecursionAllowed, false, false, DnsResponseCode.NoError, request.Question, new DnsResourceRecord[] { new DnsResourceRecord(qname, DnsResourceRecordType.A, question.Class, appRecordTtl, new DnsARecordData(responseA)) });
  196. case DnsResourceRecordType.TXT:
  197. if (!string.IsNullOrEmpty(responseTXT))
  198. return new DnsDatagram(request.Identifier, true, DnsOpcode.StandardQuery, true, false, request.RecursionDesired, isRecursionAllowed, false, false, DnsResponseCode.NoError, request.Question, new DnsResourceRecord[] { new DnsResourceRecord(qname, DnsResourceRecordType.TXT, question.Class, appRecordTtl, new DnsTXTRecordData(responseTXT)) });
  199. break;
  200. }
  201. //NODATA response
  202. DnsDatagram soaResponse = await _dnsServer.DirectQueryAsync(new DnsQuestionRecord(zoneName, DnsResourceRecordType.SOA, DnsClass.IN));
  203. return new DnsDatagram(request.Identifier, true, DnsOpcode.StandardQuery, true, false, request.RecursionDesired, isRecursionAllowed, false, false, DnsResponseCode.NoError, request.Question, null, soaResponse.Answer);
  204. }
  205. }
  206. return null;
  207. }
  208. #endregion
  209. #region properties
  210. public string Description
  211. { get { return "Returns A or TXT records based on the DNS Block Lists (DNSBL) configured in the APP record data. Returns NXDOMAIN response when an IP address or domain name is not blocked in any of the configured blocklists."; } }
  212. public string ApplicationRecordDataTemplate
  213. {
  214. get
  215. {
  216. return @"{
  217. ""dnsBlockLists"": [
  218. ""ipblocklist1"",
  219. ""domainblocklist1""
  220. ]
  221. }";
  222. }
  223. }
  224. #endregion
  225. enum BlockListType
  226. {
  227. Ip = 1,
  228. Domain = 2
  229. }
  230. abstract class BlockList : IDisposable
  231. {
  232. #region variables
  233. protected static readonly char[] _popWordSeperator = new char[] { ' ', '\t', '|' };
  234. protected readonly IDnsServer _dnsServer;
  235. readonly BlockListType _type;
  236. readonly string _name;
  237. bool _enabled;
  238. protected IPAddress _responseA;
  239. protected string _responseTXT;
  240. protected string _blockListFile;
  241. protected DateTime _blockListFileLastModified;
  242. Timer _autoReloadTimer;
  243. const int AUTO_RELOAD_TIMER_INTERVAL = 60000;
  244. #endregion
  245. #region constructor
  246. protected BlockList(IDnsServer dnsServer, BlockListType type, JsonElement jsonBlockList)
  247. {
  248. _dnsServer = dnsServer;
  249. _type = type;
  250. _name = jsonBlockList.GetProperty("name").GetString();
  251. _autoReloadTimer = new Timer(delegate (object state)
  252. {
  253. try
  254. {
  255. DateTime blockListFileLastModified = File.GetLastWriteTimeUtc(_blockListFile);
  256. if (blockListFileLastModified > _blockListFileLastModified)
  257. ReloadBlockListFile();
  258. }
  259. catch (Exception ex)
  260. {
  261. _dnsServer.WriteLog(ex);
  262. }
  263. finally
  264. {
  265. _autoReloadTimer?.Change(AUTO_RELOAD_TIMER_INTERVAL, Timeout.Infinite);
  266. }
  267. });
  268. ReloadConfig(jsonBlockList);
  269. }
  270. #endregion
  271. #region IDisposable
  272. public void Dispose()
  273. {
  274. if (_autoReloadTimer is not null)
  275. {
  276. _autoReloadTimer.Dispose();
  277. _autoReloadTimer = null;
  278. }
  279. }
  280. #endregion
  281. #region protected
  282. protected abstract void ReloadBlockListFile();
  283. protected static string PopWord(ref string line)
  284. {
  285. if (line.Length == 0)
  286. return line;
  287. line = line.TrimStart(_popWordSeperator);
  288. int i = line.IndexOfAny(_popWordSeperator);
  289. string word;
  290. if (i < 0)
  291. {
  292. word = line;
  293. line = "";
  294. }
  295. else
  296. {
  297. word = line.Substring(0, i);
  298. line = line.Substring(i + 1);
  299. }
  300. return word;
  301. }
  302. #endregion
  303. #region public
  304. public void ReloadConfig(JsonElement jsonBlockList)
  305. {
  306. _enabled = jsonBlockList.GetPropertyValue("enabled", true);
  307. _responseA = IPAddress.Parse(jsonBlockList.GetPropertyValue("responseA", "127.0.0.2"));
  308. if (jsonBlockList.TryGetProperty("responseTXT", out JsonElement jsonResponseTXT))
  309. _responseTXT = jsonResponseTXT.GetString();
  310. else
  311. _responseTXT = null;
  312. string blockListFile = jsonBlockList.GetProperty("blockListFile").GetString();
  313. if (!Path.IsPathRooted(blockListFile))
  314. blockListFile = Path.Combine(_dnsServer.ApplicationFolder, blockListFile);
  315. if (!blockListFile.Equals(_blockListFile))
  316. {
  317. _blockListFile = blockListFile;
  318. _blockListFileLastModified = default;
  319. }
  320. _autoReloadTimer.Change(0, Timeout.Infinite);
  321. }
  322. public virtual bool IsBlocked(IPAddress address, out IPAddress responseA, out string responseTXT)
  323. {
  324. throw new InvalidOperationException();
  325. }
  326. public virtual bool IsBlocked(string domain, out string foundDomain, out IPAddress responseA, out string responseTXT)
  327. {
  328. throw new InvalidOperationException();
  329. }
  330. #endregion
  331. #region properties
  332. public BlockListType Type
  333. { get { return _type; } }
  334. public string Name
  335. { get { return _name; } }
  336. public bool Enabled
  337. { get { return _enabled; } }
  338. public IPAddress ResponseA
  339. { get { return _responseA; } }
  340. public string ResponseTXT
  341. { get { return _responseTXT; } }
  342. public string BlockListFile
  343. { get { return _blockListFile; } }
  344. #endregion
  345. }
  346. class BlockEntry<T>
  347. {
  348. #region variables
  349. readonly T _key;
  350. readonly IPAddress _responseA;
  351. readonly string _responseTXT;
  352. #endregion
  353. #region constructor
  354. public BlockEntry(T key, string responseA, string responseTXT)
  355. {
  356. _key = key;
  357. if (IPAddress.TryParse(responseA, out IPAddress addr))
  358. _responseA = addr;
  359. if (!string.IsNullOrEmpty(responseTXT))
  360. _responseTXT = responseTXT;
  361. }
  362. #endregion
  363. #region properties
  364. public T Key
  365. { get { return _key; } }
  366. public IPAddress ResponseA
  367. { get { return _responseA; } }
  368. public string ResponseTXT
  369. { get { return _responseTXT; } }
  370. #endregion
  371. }
  372. class IpBlockList : BlockList
  373. {
  374. #region variables
  375. Dictionary<IPAddress, BlockEntry<IPAddress>> _ipv4Map;
  376. Dictionary<IPAddress, BlockEntry<IPAddress>> _ipv6Map;
  377. NetworkMap<BlockEntry<NetworkAddress>> _ipv4NetworkMap;
  378. NetworkMap<BlockEntry<NetworkAddress>> _ipv6NetworkMap;
  379. #endregion
  380. #region constructor
  381. public IpBlockList(IDnsServer dnsServer, JsonElement jsonBlockList)
  382. : base(dnsServer, BlockListType.Ip, jsonBlockList)
  383. { }
  384. #endregion
  385. #region protected
  386. protected override void ReloadBlockListFile()
  387. {
  388. try
  389. {
  390. _dnsServer.WriteLog("The app is reading IP block list file: " + _blockListFile);
  391. //parse ip block list file
  392. Queue<BlockEntry<IPAddress>> ipv4Addresses = new Queue<BlockEntry<IPAddress>>();
  393. Queue<BlockEntry<IPAddress>> ipv6Addresses = new Queue<BlockEntry<IPAddress>>();
  394. Queue<BlockEntry<NetworkAddress>> ipv4Networks = new Queue<BlockEntry<NetworkAddress>>();
  395. Queue<BlockEntry<NetworkAddress>> ipv6Networks = new Queue<BlockEntry<NetworkAddress>>();
  396. ipv4Addresses.Enqueue(new BlockEntry<IPAddress>(IPAddress.Parse("127.0.0.2"), "127.0.0.2", "rfc5782 test entry"));
  397. ipv6Addresses.Enqueue(new BlockEntry<IPAddress>(IPAddress.Parse("::FFFF:7F00:2"), "127.0.0.2", "rfc5782 test entry"));
  398. using (FileStream fS = new FileStream(_blockListFile, FileMode.Open, FileAccess.Read))
  399. {
  400. StreamReader sR = new StreamReader(fS, true);
  401. string line;
  402. string network;
  403. string responseA;
  404. string responseTXT;
  405. while (true)
  406. {
  407. line = sR.ReadLine();
  408. if (line is null)
  409. break; //eof
  410. line = line.TrimStart(_popWordSeperator);
  411. if (line.Length == 0)
  412. continue; //skip empty line
  413. if (line.StartsWith('#'))
  414. continue; //skip comment line
  415. network = PopWord(ref line);
  416. responseA = PopWord(ref line);
  417. responseTXT = line;
  418. if (NetworkAddress.TryParse(network, out NetworkAddress networkAddress))
  419. {
  420. switch (networkAddress.AddressFamily)
  421. {
  422. case AddressFamily.InterNetwork:
  423. if (networkAddress.PrefixLength == 32)
  424. ipv4Addresses.Enqueue(new BlockEntry<IPAddress>(networkAddress.Address, responseA, responseTXT));
  425. else
  426. ipv4Networks.Enqueue(new BlockEntry<NetworkAddress>(networkAddress, responseA, responseTXT));
  427. break;
  428. case AddressFamily.InterNetworkV6:
  429. if (networkAddress.PrefixLength == 128)
  430. ipv6Addresses.Enqueue(new BlockEntry<IPAddress>(networkAddress.Address, responseA, responseTXT));
  431. else
  432. ipv6Networks.Enqueue(new BlockEntry<NetworkAddress>(networkAddress, responseA, responseTXT));
  433. break;
  434. }
  435. }
  436. }
  437. _blockListFileLastModified = File.GetLastWriteTimeUtc(fS.SafeFileHandle);
  438. }
  439. //load ip lookup list
  440. Dictionary<IPAddress, BlockEntry<IPAddress>> ipv4AddressMap = new Dictionary<IPAddress, BlockEntry<IPAddress>>(ipv4Addresses.Count);
  441. while (ipv4Addresses.Count > 0)
  442. {
  443. BlockEntry<IPAddress> entry = ipv4Addresses.Dequeue();
  444. ipv4AddressMap.TryAdd(entry.Key, entry);
  445. }
  446. Dictionary<IPAddress, BlockEntry<IPAddress>> ipv6AddressMap = new Dictionary<IPAddress, BlockEntry<IPAddress>>(ipv6Addresses.Count);
  447. while (ipv6Addresses.Count > 0)
  448. {
  449. BlockEntry<IPAddress> entry = ipv6Addresses.Dequeue();
  450. ipv6AddressMap.TryAdd(entry.Key, entry);
  451. }
  452. NetworkMap<BlockEntry<NetworkAddress>> ipv4NetworkMap = new NetworkMap<BlockEntry<NetworkAddress>>(ipv4Networks.Count);
  453. while (ipv4Networks.Count > 0)
  454. {
  455. BlockEntry<NetworkAddress> entry = ipv4Networks.Dequeue();
  456. ipv4NetworkMap.Add(entry.Key, entry);
  457. }
  458. NetworkMap<BlockEntry<NetworkAddress>> ipv6NetworkMap = new NetworkMap<BlockEntry<NetworkAddress>>(ipv6Networks.Count);
  459. while (ipv6Networks.Count > 0)
  460. {
  461. BlockEntry<NetworkAddress> entry = ipv6Networks.Dequeue();
  462. ipv6NetworkMap.Add(entry.Key, entry);
  463. }
  464. //update
  465. _ipv4Map = ipv4AddressMap;
  466. _ipv6Map = ipv6AddressMap;
  467. _ipv4NetworkMap = ipv4NetworkMap;
  468. _ipv6NetworkMap = ipv6NetworkMap;
  469. _dnsServer.WriteLog("The app has successfully loaded IP block list file: " + _blockListFile);
  470. }
  471. catch (Exception ex)
  472. {
  473. _dnsServer.WriteLog("The app failed to read IP block list file: " + _blockListFile + "\r\n" + ex.ToString());
  474. }
  475. }
  476. #endregion
  477. #region public
  478. public override bool IsBlocked(IPAddress address, out IPAddress responseA, out string responseTXT)
  479. {
  480. switch (address.AddressFamily)
  481. {
  482. case AddressFamily.InterNetwork:
  483. {
  484. if (_ipv4Map.TryGetValue(address, out BlockEntry<IPAddress> ipEntry))
  485. {
  486. responseA = ipEntry.ResponseA is null ? _responseA : ipEntry.ResponseA;
  487. responseTXT = ipEntry.ResponseTXT is null ? _responseTXT : ipEntry.ResponseTXT;
  488. return true;
  489. }
  490. if (_ipv4NetworkMap.TryGetValue(address, out BlockEntry<NetworkAddress> networkEntry))
  491. {
  492. responseA = networkEntry.ResponseA is null ? _responseA : networkEntry.ResponseA;
  493. responseTXT = networkEntry.ResponseTXT is null ? _responseTXT : networkEntry.ResponseTXT;
  494. return true;
  495. }
  496. }
  497. break;
  498. case AddressFamily.InterNetworkV6:
  499. {
  500. if (_ipv6Map.TryGetValue(address, out BlockEntry<IPAddress> ipEntry))
  501. {
  502. responseA = ipEntry.ResponseA is null ? _responseA : ipEntry.ResponseA;
  503. responseTXT = ipEntry.ResponseTXT is null ? _responseTXT : ipEntry.ResponseTXT;
  504. return true;
  505. }
  506. if (_ipv6NetworkMap.TryGetValue(address, out BlockEntry<NetworkAddress> networkEntry))
  507. {
  508. responseA = networkEntry.ResponseA is null ? _responseA : networkEntry.ResponseA;
  509. responseTXT = networkEntry.ResponseTXT is null ? _responseTXT : networkEntry.ResponseTXT;
  510. return true;
  511. }
  512. }
  513. break;
  514. }
  515. responseA = null;
  516. responseTXT = null;
  517. return false;
  518. }
  519. #endregion
  520. }
  521. class DomainBlockList : BlockList
  522. {
  523. #region variables
  524. Dictionary<string, BlockEntry<string>> _domainMap;
  525. #endregion
  526. #region constructor
  527. public DomainBlockList(IDnsServer dnsServer, JsonElement jsonIpBlockList)
  528. : base(dnsServer, BlockListType.Domain, jsonIpBlockList)
  529. { }
  530. #endregion
  531. #region protected
  532. protected override void ReloadBlockListFile()
  533. {
  534. try
  535. {
  536. _dnsServer.WriteLog("The app is reading domain block list file: " + _blockListFile);
  537. //parse ip block list file
  538. Queue<BlockEntry<string>> domains = new Queue<BlockEntry<string>>();
  539. domains.Enqueue(new BlockEntry<string>("test", "127.0.0.2", "rfc5782 test entry"));
  540. using (FileStream fS = new FileStream(_blockListFile, FileMode.Open, FileAccess.Read))
  541. {
  542. StreamReader sR = new StreamReader(fS, true);
  543. char[] trimSeperator = new char[] { ' ', '\t', ':', '|', ',' };
  544. string line;
  545. string domain;
  546. string responseA;
  547. string responseTXT;
  548. while (true)
  549. {
  550. line = sR.ReadLine();
  551. if (line is null)
  552. break; //eof
  553. line = line.TrimStart(trimSeperator);
  554. if (line.Length == 0)
  555. continue; //skip empty line
  556. if (line.StartsWith('#'))
  557. continue; //skip comment line
  558. domain = PopWord(ref line);
  559. responseA = PopWord(ref line);
  560. responseTXT = line;
  561. if (DnsClient.IsDomainNameValid(domain))
  562. domains.Enqueue(new BlockEntry<string>(domain.ToLower(), responseA, responseTXT));
  563. }
  564. _blockListFileLastModified = File.GetLastWriteTimeUtc(fS.SafeFileHandle);
  565. }
  566. //load ip lookup list
  567. Dictionary<string, BlockEntry<string>> domainMap = new Dictionary<string, BlockEntry<string>>(domains.Count);
  568. while (domains.Count > 0)
  569. {
  570. BlockEntry<string> entry = domains.Dequeue();
  571. domainMap.TryAdd(entry.Key, entry);
  572. }
  573. //update
  574. _domainMap = domainMap;
  575. _dnsServer.WriteLog("The app has successfully loaded domain block list file: " + _blockListFile);
  576. }
  577. catch (Exception ex)
  578. {
  579. _dnsServer.WriteLog("The app failed to read domain block list file: " + _blockListFile + "\r\n" + ex.ToString());
  580. }
  581. }
  582. #endregion
  583. #region private
  584. private static string GetParentZone(string domain)
  585. {
  586. int i = domain.IndexOf('.');
  587. if (i > -1)
  588. return domain.Substring(i + 1);
  589. //dont return root zone
  590. return null;
  591. }
  592. private bool IsDomainBlocked(string domain, out BlockEntry<string> domainEntry)
  593. {
  594. do
  595. {
  596. if (_domainMap.TryGetValue(domain, out domainEntry))
  597. {
  598. return true;
  599. }
  600. domain = GetParentZone(domain);
  601. }
  602. while (domain is not null);
  603. return false;
  604. }
  605. #endregion
  606. #region public
  607. public override bool IsBlocked(string domain, out string foundDomain, out IPAddress responseA, out string responseTXT)
  608. {
  609. if (IsDomainBlocked(domain.ToLower(), out BlockEntry<string> domainEntry))
  610. {
  611. foundDomain = domainEntry.Key;
  612. responseA = domainEntry.ResponseA is null ? _responseA : domainEntry.ResponseA;
  613. responseTXT = domainEntry.ResponseTXT is null ? _responseTXT : domainEntry.ResponseTXT;
  614. return true;
  615. }
  616. foundDomain = null;
  617. responseA = null;
  618. responseTXT = null;
  619. return false;
  620. }
  621. #endregion
  622. }
  623. }
  624. }