App.cs 67 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545154615471548154915501551155215531554155515561557155815591560156115621563156415651566156715681569157015711572157315741575157615771578157915801581158215831584158515861587158815891590159115921593159415951596159715981599160016011602160316041605160616071608160916101611161216131614161516161617161816191620162116221623162416251626162716281629163016311632163316341635163616371638163916401641164216431644164516461647164816491650165116521653165416551656165716581659166016611662166316641665166616671668166916701671167216731674167516761677167816791680168116821683168416851686168716881689
  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.IO;
  19. using System.Net;
  20. using System.Net.Http;
  21. using System.Net.Sockets;
  22. using System.Security.Cryptography;
  23. using System.Text;
  24. using System.Text.Json;
  25. using System.Text.RegularExpressions;
  26. using System.Threading;
  27. using System.Threading.Tasks;
  28. using TechnitiumLibrary;
  29. using TechnitiumLibrary.Net;
  30. using TechnitiumLibrary.Net.Dns;
  31. using TechnitiumLibrary.Net.Dns.EDnsOptions;
  32. using TechnitiumLibrary.Net.Dns.ResourceRecords;
  33. using TechnitiumLibrary.Net.Http.Client;
  34. namespace AdvancedBlocking
  35. {
  36. public sealed class App : IDnsApplication, IDnsRequestBlockingHandler
  37. {
  38. #region variables
  39. IDnsServer _dnsServer;
  40. DnsSOARecordData _soaRecord;
  41. DnsNSRecordData _nsRecord;
  42. bool _enableBlocking;
  43. int _blockListUrlUpdateIntervalHours;
  44. Dictionary<EndPoint, string> _localEndPointGroupMap;
  45. Dictionary<NetworkAddress, string> _networkGroupMap;
  46. Dictionary<string, Group> _groups;
  47. Dictionary<Uri, BlockList> _allAllowListZones = new Dictionary<Uri, BlockList>(0);
  48. Dictionary<Uri, BlockList> _allBlockListZones = new Dictionary<Uri, BlockList>(0);
  49. Dictionary<Uri, RegexList> _allRegexAllowListZones = new Dictionary<Uri, RegexList>(0);
  50. Dictionary<Uri, RegexList> _allRegexBlockListZones = new Dictionary<Uri, RegexList>(0);
  51. Dictionary<Uri, AdBlockList> _allAdBlockListZones = new Dictionary<Uri, AdBlockList>(0);
  52. Timer _blockListUrlUpdateTimer;
  53. DateTime _blockListUrlLastUpdatedOn;
  54. const int BLOCK_LIST_UPDATE_TIMER_INTERVAL = 900000;
  55. #endregion
  56. #region IDisposable
  57. public void Dispose()
  58. {
  59. if (_blockListUrlUpdateTimer is not null)
  60. {
  61. _blockListUrlUpdateTimer.Dispose();
  62. _blockListUrlUpdateTimer = null;
  63. }
  64. }
  65. #endregion
  66. #region private
  67. private async void BlockListUrlUpdateTimerCallbackAsync(object state)
  68. {
  69. try
  70. {
  71. if (DateTime.UtcNow > _blockListUrlLastUpdatedOn.AddHours(_blockListUrlUpdateIntervalHours))
  72. {
  73. if (await UpdateAllListsAsync())
  74. {
  75. //block lists were updated
  76. //save last updated on time
  77. _blockListUrlLastUpdatedOn = DateTime.UtcNow;
  78. }
  79. }
  80. }
  81. catch (Exception ex)
  82. {
  83. _dnsServer.WriteLog(ex);
  84. }
  85. }
  86. private async Task<bool> UpdateAllListsAsync()
  87. {
  88. List<Task<bool>> updateTasks = new List<Task<bool>>();
  89. foreach (KeyValuePair<Uri, BlockList> allAllowListZone in _allAllowListZones)
  90. updateTasks.Add(allAllowListZone.Value.UpdateAsync());
  91. foreach (KeyValuePair<Uri, BlockList> allBlockListZone in _allBlockListZones)
  92. updateTasks.Add(allBlockListZone.Value.UpdateAsync());
  93. foreach (KeyValuePair<Uri, RegexList> allRegexAllowListZone in _allRegexAllowListZones)
  94. updateTasks.Add(allRegexAllowListZone.Value.UpdateAsync());
  95. foreach (KeyValuePair<Uri, RegexList> allRegexBlockListZone in _allRegexBlockListZones)
  96. updateTasks.Add(allRegexBlockListZone.Value.UpdateAsync());
  97. foreach (KeyValuePair<Uri, AdBlockList> allAdBlockListZone in _allAdBlockListZones)
  98. updateTasks.Add(allAdBlockListZone.Value.UpdateAsync());
  99. await Task.WhenAll(updateTasks);
  100. foreach (Task<bool> updateTask in updateTasks)
  101. {
  102. bool downloaded = await updateTask;
  103. if (downloaded)
  104. return true;
  105. }
  106. return false;
  107. }
  108. private static string GetParentZone(string domain)
  109. {
  110. int i = domain.IndexOf('.');
  111. if (i > -1)
  112. return domain.Substring(i + 1);
  113. //dont return root zone
  114. return null;
  115. }
  116. private static bool IsZoneFound(Dictionary<string, object> domains, string domain, out string foundZone)
  117. {
  118. do
  119. {
  120. if (domains.TryGetValue(domain, out _))
  121. {
  122. foundZone = domain;
  123. return true;
  124. }
  125. domain = GetParentZone(domain);
  126. }
  127. while (domain is not null);
  128. foundZone = null;
  129. return false;
  130. }
  131. private static bool IsZoneFound(Dictionary<Uri, BlockList> listZones, string domain, out string foundZone, out Uri listUri)
  132. {
  133. foreach (KeyValuePair<Uri, BlockList> listZone in listZones)
  134. {
  135. if (listZone.Value.IsZoneFound(domain, out foundZone))
  136. {
  137. listUri = listZone.Key;
  138. return true;
  139. }
  140. }
  141. foundZone = null;
  142. listUri = null;
  143. return false;
  144. }
  145. private static bool IsZoneFound(Dictionary<Uri, ListZoneEntry<BlockList>> listZones, string domain, out string foundZone, out UrlEntry listUri)
  146. {
  147. foreach (KeyValuePair<Uri, ListZoneEntry<BlockList>> listZone in listZones)
  148. {
  149. if (listZone.Value.List.IsZoneFound(domain, out foundZone))
  150. {
  151. listUri = listZone.Value.UrlEntry;
  152. return true;
  153. }
  154. }
  155. foundZone = null;
  156. listUri = null;
  157. return false;
  158. }
  159. private static bool IsZoneAllowed(Dictionary<Uri, ListZoneEntry<AdBlockList>> listZones, string domain, out string foundZone, out UrlEntry listUri)
  160. {
  161. foreach (KeyValuePair<Uri, ListZoneEntry<AdBlockList>> listZone in listZones)
  162. {
  163. if (listZone.Value.List.IsZoneAllowed(domain, out foundZone))
  164. {
  165. listUri = listZone.Value.UrlEntry;
  166. return true;
  167. }
  168. }
  169. foundZone = null;
  170. listUri = null;
  171. return false;
  172. }
  173. private static bool IsZoneBlocked(Dictionary<Uri, ListZoneEntry<AdBlockList>> listZones, string domain, out string foundZone, out UrlEntry listUri)
  174. {
  175. foreach (KeyValuePair<Uri, ListZoneEntry<AdBlockList>> listZone in listZones)
  176. {
  177. if (listZone.Value.List.IsZoneBlocked(domain, out foundZone))
  178. {
  179. listUri = listZone.Value.UrlEntry;
  180. return true;
  181. }
  182. }
  183. foundZone = null;
  184. listUri = null;
  185. return false;
  186. }
  187. private static bool IsMatchFound(Regex[] regices, string domain, out string matchingPattern)
  188. {
  189. foreach (Regex regex in regices)
  190. {
  191. if (regex.IsMatch(domain))
  192. {
  193. //found pattern
  194. matchingPattern = regex.ToString();
  195. return true;
  196. }
  197. }
  198. matchingPattern = null;
  199. return false;
  200. }
  201. private static bool IsMatchFound(Dictionary<Uri, RegexList> regexListZones, string domain, out string matchingPattern, out Uri listUri)
  202. {
  203. foreach (KeyValuePair<Uri, RegexList> regexListZone in regexListZones)
  204. {
  205. if (regexListZone.Value.IsMatchFound(domain, out matchingPattern))
  206. {
  207. listUri = regexListZone.Key;
  208. return true;
  209. }
  210. }
  211. matchingPattern = null;
  212. listUri = null;
  213. return false;
  214. }
  215. private static bool IsMatchFound(Dictionary<Uri, ListZoneEntry<RegexList>> regexListZones, string domain, out string matchingPattern, out UrlEntry listUri)
  216. {
  217. foreach (KeyValuePair<Uri, ListZoneEntry<RegexList>> regexListZone in regexListZones)
  218. {
  219. if (regexListZone.Value.List.IsMatchFound(domain, out matchingPattern))
  220. {
  221. listUri = regexListZone.Value.UrlEntry;
  222. return true;
  223. }
  224. }
  225. matchingPattern = null;
  226. listUri = null;
  227. return false;
  228. }
  229. private string GetGroupName(DnsDatagram request, IPEndPoint remoteEP)
  230. {
  231. if ((request.Metadata is not null) && (request.Metadata.NameServer is not null))
  232. {
  233. Uri requestLocalUriEP = request.Metadata.NameServer.DoHEndPoint;
  234. if (requestLocalUriEP is not null)
  235. {
  236. foreach (KeyValuePair<EndPoint, string> entry in _localEndPointGroupMap)
  237. {
  238. if (entry.Key is DomainEndPoint ep)
  239. {
  240. if (((ep.Port == 0) || (ep.Port == requestLocalUriEP.Port)) && ep.Address.Equals(requestLocalUriEP.Host, StringComparison.OrdinalIgnoreCase))
  241. return entry.Value;
  242. }
  243. }
  244. }
  245. DomainEndPoint requestLocalDomainEP = request.Metadata.NameServer.DomainEndPoint;
  246. if (requestLocalDomainEP is not null)
  247. {
  248. foreach (KeyValuePair<EndPoint, string> entry in _localEndPointGroupMap)
  249. {
  250. if (entry.Key is DomainEndPoint ep)
  251. {
  252. if (((ep.Port == 0) || (ep.Port == requestLocalDomainEP.Port)) && ep.Address.Equals(requestLocalDomainEP.Address, StringComparison.OrdinalIgnoreCase))
  253. return entry.Value;
  254. }
  255. }
  256. }
  257. IPEndPoint requestLocalEP = request.Metadata.NameServer.IPEndPoint;
  258. if (requestLocalEP is not null)
  259. {
  260. foreach (KeyValuePair<EndPoint, string> entry in _localEndPointGroupMap)
  261. {
  262. if (entry.Key is IPEndPoint ep)
  263. {
  264. if (((ep.Port == 0) || (ep.Port == requestLocalEP.Port)) && ep.Address.Equals(requestLocalEP.Address))
  265. return entry.Value;
  266. }
  267. }
  268. }
  269. }
  270. string groupName = null;
  271. IPAddress remoteIP = remoteEP.Address;
  272. NetworkAddress network = null;
  273. foreach (KeyValuePair<NetworkAddress, string> entry in _networkGroupMap)
  274. {
  275. if (entry.Key.Contains(remoteIP) && ((network is null) || (entry.Key.PrefixLength > network.PrefixLength)))
  276. {
  277. network = entry.Key;
  278. groupName = entry.Value;
  279. }
  280. }
  281. return groupName;
  282. }
  283. #endregion
  284. #region public
  285. public async Task InitializeAsync(IDnsServer dnsServer, string config)
  286. {
  287. _dnsServer = dnsServer;
  288. Directory.CreateDirectory(Path.Combine(_dnsServer.ApplicationFolder, "blocklists"));
  289. _soaRecord = new DnsSOARecordData(_dnsServer.ServerDomain, _dnsServer.ResponsiblePerson.Address, 1, 14400, 3600, 604800, 60);
  290. _nsRecord = new DnsNSRecordData(_dnsServer.ServerDomain);
  291. using JsonDocument jsonDocument = JsonDocument.Parse(config);
  292. JsonElement jsonConfig = jsonDocument.RootElement;
  293. _enableBlocking = jsonConfig.GetProperty("enableBlocking").GetBoolean();
  294. _blockListUrlUpdateIntervalHours = jsonConfig.GetProperty("blockListUrlUpdateIntervalHours").GetInt32();
  295. if (jsonConfig.TryReadObjectAsMap("localEndPointGroupMap",
  296. delegate (string localEP, JsonElement jsonGroup)
  297. {
  298. if (!EndPointExtensions.TryParse(localEP, out EndPoint ep))
  299. throw new InvalidOperationException("Local end point group map contains an invalid end point: " + localEP);
  300. return new Tuple<EndPoint, string>(ep, jsonGroup.GetString());
  301. },
  302. out Dictionary<EndPoint, string> localEndPointGroupMap))
  303. {
  304. _localEndPointGroupMap = localEndPointGroupMap;
  305. }
  306. _networkGroupMap = jsonConfig.ReadObjectAsMap("networkGroupMap", delegate (string network, JsonElement jsonGroup)
  307. {
  308. if (!NetworkAddress.TryParse(network, out NetworkAddress networkAddress))
  309. throw new InvalidOperationException("Network group map contains an invalid network address: " + network);
  310. return new Tuple<NetworkAddress, string>(networkAddress, jsonGroup.GetString());
  311. });
  312. {
  313. Dictionary<Uri, BlockList> allAllowListZones = new Dictionary<Uri, BlockList>(0);
  314. Dictionary<Uri, BlockList> allBlockListZones = new Dictionary<Uri, BlockList>(0);
  315. Dictionary<Uri, RegexList> allRegexAllowListZones = new Dictionary<Uri, RegexList>(0);
  316. Dictionary<Uri, RegexList> allRegexBlockListZones = new Dictionary<Uri, RegexList>(0);
  317. Dictionary<Uri, AdBlockList> allAdBlockListZones = new Dictionary<Uri, AdBlockList>(0);
  318. _groups = jsonConfig.ReadArrayAsMap("groups", delegate (JsonElement jsonGroup)
  319. {
  320. Group group = new Group(this, jsonGroup);
  321. foreach (Uri allowListUrl in group.AllowListUrls)
  322. {
  323. if (!allAllowListZones.ContainsKey(allowListUrl))
  324. {
  325. if (_allAllowListZones.TryGetValue(allowListUrl, out BlockList allowList))
  326. allAllowListZones.Add(allowListUrl, allowList);
  327. else
  328. allAllowListZones.Add(allowListUrl, new BlockList(_dnsServer, allowListUrl, true));
  329. }
  330. }
  331. foreach (UrlEntry blockListUrl in group.BlockListUrls)
  332. {
  333. if (!allBlockListZones.ContainsKey(blockListUrl.Uri))
  334. {
  335. if (_allBlockListZones.TryGetValue(blockListUrl.Uri, out BlockList blockList))
  336. allBlockListZones.Add(blockListUrl.Uri, blockList);
  337. else
  338. allBlockListZones.Add(blockListUrl.Uri, new BlockList(_dnsServer, blockListUrl.Uri, false));
  339. }
  340. }
  341. foreach (Uri regexAllowListUrl in group.RegexAllowListUrls)
  342. {
  343. if (!allRegexAllowListZones.ContainsKey(regexAllowListUrl))
  344. {
  345. if (_allRegexAllowListZones.TryGetValue(regexAllowListUrl, out RegexList regexAllowList))
  346. allRegexAllowListZones.Add(regexAllowListUrl, regexAllowList);
  347. else
  348. allRegexAllowListZones.Add(regexAllowListUrl, new RegexList(_dnsServer, regexAllowListUrl, true));
  349. }
  350. }
  351. foreach (UrlEntry regexBlockListUrl in group.RegexBlockListUrls)
  352. {
  353. if (!allRegexBlockListZones.ContainsKey(regexBlockListUrl.Uri))
  354. {
  355. if (_allRegexBlockListZones.TryGetValue(regexBlockListUrl.Uri, out RegexList regexBlockList))
  356. allRegexBlockListZones.Add(regexBlockListUrl.Uri, regexBlockList);
  357. else
  358. allRegexBlockListZones.Add(regexBlockListUrl.Uri, new RegexList(_dnsServer, regexBlockListUrl.Uri, false));
  359. }
  360. }
  361. foreach (UrlEntry adblockListUrl in group.AdblockListUrls)
  362. {
  363. if (!allAdBlockListZones.ContainsKey(adblockListUrl.Uri))
  364. {
  365. if (_allAdBlockListZones.TryGetValue(adblockListUrl.Uri, out AdBlockList adBlockList))
  366. allAdBlockListZones.Add(adblockListUrl.Uri, adBlockList);
  367. else
  368. allAdBlockListZones.Add(adblockListUrl.Uri, new AdBlockList(_dnsServer, adblockListUrl.Uri));
  369. }
  370. }
  371. return new Tuple<string, Group>(group.Name, group);
  372. });
  373. _allAllowListZones = allAllowListZones;
  374. _allBlockListZones = allBlockListZones;
  375. _allRegexAllowListZones = allRegexAllowListZones;
  376. _allRegexBlockListZones = allRegexBlockListZones;
  377. _allAdBlockListZones = allAdBlockListZones;
  378. }
  379. foreach (KeyValuePair<string, Group> group in _groups)
  380. {
  381. group.Value.LoadListZones();
  382. _dnsServer.WriteLog("Advanced Blocking app loaded all zones successfully for group: " + group.Key);
  383. }
  384. _ = Task.Run(async delegate ()
  385. {
  386. List<Task> loadTasks = new List<Task>();
  387. foreach (KeyValuePair<Uri, BlockList> allAllowListZone in _allAllowListZones)
  388. loadTasks.Add(allAllowListZone.Value.LoadAsync());
  389. foreach (KeyValuePair<Uri, BlockList> allBlockListZone in _allBlockListZones)
  390. loadTasks.Add(allBlockListZone.Value.LoadAsync());
  391. foreach (KeyValuePair<Uri, RegexList> allRegexAllowListZone in _allRegexAllowListZones)
  392. loadTasks.Add(allRegexAllowListZone.Value.LoadAsync());
  393. foreach (KeyValuePair<Uri, RegexList> allRegexBlockListZone in _allRegexBlockListZones)
  394. loadTasks.Add(allRegexBlockListZone.Value.LoadAsync());
  395. foreach (KeyValuePair<Uri, AdBlockList> allAdBlockListZone in _allAdBlockListZones)
  396. loadTasks.Add(allAdBlockListZone.Value.LoadAsync());
  397. await Task.WhenAll(loadTasks);
  398. if (_blockListUrlUpdateTimer is null)
  399. {
  400. DateTime latest = DateTime.MinValue;
  401. foreach (KeyValuePair<Uri, BlockList> allAllowListZone in _allAllowListZones)
  402. {
  403. if (allAllowListZone.Value.LastModified > latest)
  404. latest = allAllowListZone.Value.LastModified;
  405. }
  406. foreach (KeyValuePair<Uri, BlockList> allBlockListZone in _allBlockListZones)
  407. {
  408. if (allBlockListZone.Value.LastModified > latest)
  409. latest = allBlockListZone.Value.LastModified;
  410. }
  411. foreach (KeyValuePair<Uri, RegexList> allRegexAllowListZone in _allRegexAllowListZones)
  412. {
  413. if (allRegexAllowListZone.Value.LastModified > latest)
  414. latest = allRegexAllowListZone.Value.LastModified;
  415. }
  416. foreach (KeyValuePair<Uri, RegexList> allRegexBlockListZone in _allRegexBlockListZones)
  417. {
  418. if (allRegexBlockListZone.Value.LastModified > latest)
  419. latest = allRegexBlockListZone.Value.LastModified;
  420. }
  421. foreach (KeyValuePair<Uri, AdBlockList> allAdBlockListZone in _allAdBlockListZones)
  422. {
  423. if (allAdBlockListZone.Value.LastModified > latest)
  424. latest = allAdBlockListZone.Value.LastModified;
  425. }
  426. _blockListUrlLastUpdatedOn = latest;
  427. _blockListUrlUpdateTimer = new Timer(BlockListUrlUpdateTimerCallbackAsync, null, Timeout.Infinite, Timeout.Infinite);
  428. _blockListUrlUpdateTimer.Change(BLOCK_LIST_UPDATE_TIMER_INTERVAL, BLOCK_LIST_UPDATE_TIMER_INTERVAL);
  429. }
  430. });
  431. if (!jsonConfig.TryGetProperty("localEndPointGroupMap", out _))
  432. {
  433. config = config.Replace("\"networkGroupMap\"", "\"localEndPointGroupMap\": {\r\n },\r\n \"networkGroupMap\"");
  434. await File.WriteAllTextAsync(Path.Combine(dnsServer.ApplicationFolder, "dnsApp.config"), config);
  435. }
  436. }
  437. public Task<bool> IsAllowedAsync(DnsDatagram request, IPEndPoint remoteEP)
  438. {
  439. if (!_enableBlocking)
  440. return Task.FromResult(false);
  441. string groupName = GetGroupName(request, remoteEP);
  442. if ((groupName is null) || !_groups.TryGetValue(groupName, out Group group) || !group.EnableBlocking)
  443. return Task.FromResult(false);
  444. DnsQuestionRecord question = request.Question[0];
  445. return Task.FromResult(group.IsZoneAllowed(question.Name));
  446. }
  447. public Task<DnsDatagram> ProcessRequestAsync(DnsDatagram request, IPEndPoint remoteEP)
  448. {
  449. if (!_enableBlocking)
  450. return Task.FromResult<DnsDatagram>(null);
  451. string groupName = GetGroupName(request, remoteEP);
  452. if ((groupName is null) || !_groups.TryGetValue(groupName, out Group group) || !group.EnableBlocking)
  453. return Task.FromResult<DnsDatagram>(null);
  454. DnsQuestionRecord question = request.Question[0];
  455. if (!group.IsZoneBlocked(question.Name, out string blockedDomain, out string blockedRegex, out UrlEntry blockListUrl))
  456. return Task.FromResult<DnsDatagram>(null);
  457. string GetBlockingReport()
  458. {
  459. string blockingReport = "source=advanced-blocking-app; group=" + group.Name;
  460. if (blockedRegex is null)
  461. {
  462. if (blockListUrl.Uri is not null)
  463. blockingReport += "; blockListUrl=" + blockListUrl.Uri.AbsoluteUri + "; domain=" + blockedDomain;
  464. else
  465. blockingReport += "; domain=" + blockedDomain;
  466. }
  467. else
  468. {
  469. if (blockListUrl.Uri is not null)
  470. blockingReport += "; regexBlockListUrl=" + blockListUrl.Uri.AbsoluteUri + "; regex=" + blockedRegex;
  471. else
  472. blockingReport += "; regex=" + blockedRegex;
  473. }
  474. return blockingReport;
  475. }
  476. if (group.AllowTxtBlockingReport && (question.Type == DnsResourceRecordType.TXT))
  477. {
  478. //return meta data
  479. string blockingReport = GetBlockingReport();
  480. DnsResourceRecord[] answer = new DnsResourceRecord[] { new DnsResourceRecord(question.Name, DnsResourceRecordType.TXT, question.Class, 60, new DnsTXTRecordData(blockingReport)) };
  481. return Task.FromResult(new DnsDatagram(request.Identifier, true, DnsOpcode.StandardQuery, false, false, request.RecursionDesired, false, false, false, DnsResponseCode.NoError, request.Question, answer));
  482. }
  483. else
  484. {
  485. EDnsOption[] options = null;
  486. if (group.AllowTxtBlockingReport && (request.EDNS is not null))
  487. {
  488. string blockingReport = GetBlockingReport();
  489. options = new EDnsOption[] { new EDnsOption(EDnsOptionCode.EXTENDED_DNS_ERROR, new EDnsExtendedDnsErrorOptionData(EDnsExtendedDnsErrorCode.Blocked, blockingReport)) };
  490. }
  491. DnsResponseCode rcode;
  492. IReadOnlyList<DnsResourceRecord> answer = null;
  493. IReadOnlyList<DnsResourceRecord> authority = null;
  494. if (blockListUrl.BlockAsNxDomain)
  495. {
  496. rcode = DnsResponseCode.NxDomain;
  497. if (blockedDomain is null)
  498. blockedDomain = question.Name;
  499. string parentDomain = GetParentZone(blockedDomain);
  500. if (parentDomain is null)
  501. parentDomain = string.Empty;
  502. authority = new DnsResourceRecord[] { new DnsResourceRecord(parentDomain, DnsResourceRecordType.SOA, question.Class, 60, _soaRecord) };
  503. }
  504. else
  505. {
  506. rcode = DnsResponseCode.NoError;
  507. switch (question.Type)
  508. {
  509. case DnsResourceRecordType.A:
  510. {
  511. List<DnsResourceRecord> rrList = new List<DnsResourceRecord>(blockListUrl.ARecords.Count);
  512. foreach (DnsARecordData record in blockListUrl.ARecords)
  513. rrList.Add(new DnsResourceRecord(question.Name, DnsResourceRecordType.A, question.Class, 60, record));
  514. answer = rrList;
  515. }
  516. break;
  517. case DnsResourceRecordType.AAAA:
  518. {
  519. List<DnsResourceRecord> rrList = new List<DnsResourceRecord>(blockListUrl.AAAARecords.Count);
  520. foreach (DnsAAAARecordData record in blockListUrl.AAAARecords)
  521. rrList.Add(new DnsResourceRecord(question.Name, DnsResourceRecordType.AAAA, question.Class, 60, record));
  522. answer = rrList;
  523. }
  524. break;
  525. case DnsResourceRecordType.NS:
  526. if (blockedDomain is null)
  527. blockedDomain = question.Name;
  528. if (question.Name.Equals(blockedDomain, StringComparison.OrdinalIgnoreCase))
  529. answer = new DnsResourceRecord[] { new DnsResourceRecord(blockedDomain, DnsResourceRecordType.NS, question.Class, 60, _nsRecord) };
  530. else
  531. authority = new DnsResourceRecord[] { new DnsResourceRecord(blockedDomain, DnsResourceRecordType.SOA, question.Class, 60, _soaRecord) };
  532. break;
  533. case DnsResourceRecordType.SOA:
  534. if (blockedDomain is null)
  535. blockedDomain = question.Name;
  536. answer = new DnsResourceRecord[] { new DnsResourceRecord(blockedDomain, DnsResourceRecordType.SOA, question.Class, 60, _soaRecord) };
  537. break;
  538. default:
  539. if (blockedDomain is null)
  540. blockedDomain = question.Name;
  541. authority = new DnsResourceRecord[] { new DnsResourceRecord(blockedDomain, DnsResourceRecordType.SOA, question.Class, 60, _soaRecord) };
  542. break;
  543. }
  544. }
  545. return Task.FromResult(new DnsDatagram(request.Identifier, true, DnsOpcode.StandardQuery, false, false, request.RecursionDesired, false, false, false, rcode, request.Question, answer, authority, null, request.EDNS is null ? ushort.MinValue : _dnsServer.UdpPayloadSize, EDnsHeaderFlags.None, options));
  546. }
  547. }
  548. #endregion
  549. #region properties
  550. public string Description
  551. { get { return "Blocks domain names using block lists and regex block lists. Supports creating groups based on client's IP address or subnet to enforce different block lists and regex block lists for each group."; } }
  552. #endregion
  553. class UrlEntry
  554. {
  555. #region variables
  556. readonly Uri _uri;
  557. readonly bool _blockAsNxDomain;
  558. readonly List<DnsARecordData> _aRecords;
  559. readonly List<DnsAAAARecordData> _aaaaRecords;
  560. #endregion
  561. #region constructor
  562. public UrlEntry(Uri uri, Group group)
  563. {
  564. _uri = uri;
  565. _blockAsNxDomain = group.BlockAsNxDomain;
  566. _aRecords = group.ARecords;
  567. _aaaaRecords = group.AAAARecords;
  568. }
  569. public UrlEntry(JsonElement jsonUrl, Group group)
  570. {
  571. switch (jsonUrl.ValueKind)
  572. {
  573. case JsonValueKind.String:
  574. _uri = new Uri(jsonUrl.GetString());
  575. _blockAsNxDomain = group.BlockAsNxDomain;
  576. _aRecords = group.ARecords;
  577. _aaaaRecords = group.AAAARecords;
  578. break;
  579. case JsonValueKind.Object:
  580. _uri = new Uri(jsonUrl.GetProperty("url").GetString());
  581. if (jsonUrl.TryGetProperty("blockAsNxDomain", out JsonElement jsonBlockAsNxDomain))
  582. _blockAsNxDomain = jsonBlockAsNxDomain.GetBoolean();
  583. else
  584. _blockAsNxDomain = group.BlockAsNxDomain;
  585. if (jsonUrl.TryGetProperty("blockingAddresses", out JsonElement jsonBlockingAddresses))
  586. {
  587. List<DnsARecordData> aRecords = new List<DnsARecordData>();
  588. List<DnsAAAARecordData> aaaaRecords = new List<DnsAAAARecordData>();
  589. foreach (JsonElement jsonBlockingAddress in jsonBlockingAddresses.EnumerateArray())
  590. {
  591. string strAddress = jsonBlockingAddress.GetString();
  592. if (IPAddress.TryParse(strAddress, out IPAddress address))
  593. {
  594. switch (address.AddressFamily)
  595. {
  596. case AddressFamily.InterNetwork:
  597. aRecords.Add(new DnsARecordData(address));
  598. break;
  599. case AddressFamily.InterNetworkV6:
  600. aaaaRecords.Add(new DnsAAAARecordData(address));
  601. break;
  602. }
  603. }
  604. }
  605. _aRecords = aRecords.Count > 0 ? aRecords : group.ARecords;
  606. _aaaaRecords = aaaaRecords.Count > 0 ? aaaaRecords : group.AAAARecords;
  607. }
  608. else
  609. {
  610. _aRecords = group.ARecords;
  611. _aaaaRecords = group.AAAARecords;
  612. }
  613. break;
  614. default:
  615. throw new InvalidDataException("Unexpected URL format: " + jsonUrl.ValueKind);
  616. }
  617. }
  618. #endregion
  619. #region properties
  620. public Uri Uri
  621. { get { return _uri; } }
  622. public bool BlockAsNxDomain
  623. { get { return _blockAsNxDomain; } }
  624. public List<DnsARecordData> ARecords
  625. { get { return _aRecords; } }
  626. public List<DnsAAAARecordData> AAAARecords
  627. { get { return _aaaaRecords; } }
  628. #endregion
  629. }
  630. class ListZoneEntry<T> where T : ListBase
  631. {
  632. #region variables
  633. readonly UrlEntry _urlEntry;
  634. readonly T _list;
  635. #endregion
  636. #region constructor
  637. public ListZoneEntry(UrlEntry urlEntry, T list)
  638. {
  639. _urlEntry = urlEntry;
  640. _list = list;
  641. }
  642. #endregion
  643. #region public
  644. public UrlEntry UrlEntry
  645. { get { return _urlEntry; } }
  646. public T List
  647. { get { return _list; } }
  648. #endregion
  649. }
  650. class Group
  651. {
  652. #region variables
  653. readonly App _app;
  654. readonly string _name;
  655. readonly bool _enableBlocking;
  656. readonly bool _allowTxtBlockingReport;
  657. readonly bool _blockAsNxDomain;
  658. readonly List<DnsARecordData> _aRecords;
  659. readonly List<DnsAAAARecordData> _aaaaRecords;
  660. readonly Dictionary<string, object> _allowed;
  661. readonly Dictionary<string, object> _blocked;
  662. readonly Uri[] _allowListUrls;
  663. readonly UrlEntry[] _blockListUrls;
  664. readonly Regex[] _allowedRegex;
  665. readonly Regex[] _blockedRegex;
  666. readonly Uri[] _regexAllowListUrls;
  667. readonly UrlEntry[] _regexBlockListUrls;
  668. readonly UrlEntry[] _adblockListUrls;
  669. Dictionary<Uri, BlockList> _allowListZones = new Dictionary<Uri, BlockList>(0);
  670. Dictionary<Uri, ListZoneEntry<BlockList>> _blockListZones = new Dictionary<Uri, ListZoneEntry<BlockList>>(0);
  671. Dictionary<Uri, RegexList> _regexAllowListZones = new Dictionary<Uri, RegexList>(0);
  672. Dictionary<Uri, ListZoneEntry<RegexList>> _regexBlockListZones = new Dictionary<Uri, ListZoneEntry<RegexList>>(0);
  673. Dictionary<Uri, ListZoneEntry<AdBlockList>> _adBlockListZones = new Dictionary<Uri, ListZoneEntry<AdBlockList>>(0);
  674. #endregion
  675. #region constructor
  676. public Group(App app, JsonElement jsonGroup)
  677. {
  678. _app = app;
  679. _name = jsonGroup.GetProperty("name").GetString();
  680. _enableBlocking = jsonGroup.GetProperty("enableBlocking").GetBoolean();
  681. _allowTxtBlockingReport = jsonGroup.GetProperty("allowTxtBlockingReport").GetBoolean();
  682. _blockAsNxDomain = jsonGroup.GetPropertyValue("blockAsNxDomain", false);
  683. if (jsonGroup.TryGetProperty("blockingAddresses", out JsonElement jsonBlockingAddresses))
  684. {
  685. List<DnsARecordData> aRecords = new List<DnsARecordData>();
  686. List<DnsAAAARecordData> aaaaRecords = new List<DnsAAAARecordData>();
  687. foreach (JsonElement jsonBlockingAddress in jsonBlockingAddresses.EnumerateArray())
  688. {
  689. string strAddress = jsonBlockingAddress.GetString();
  690. if (IPAddress.TryParse(strAddress, out IPAddress address))
  691. {
  692. switch (address.AddressFamily)
  693. {
  694. case AddressFamily.InterNetwork:
  695. aRecords.Add(new DnsARecordData(address));
  696. break;
  697. case AddressFamily.InterNetworkV6:
  698. aaaaRecords.Add(new DnsAAAARecordData(address));
  699. break;
  700. }
  701. }
  702. }
  703. _aRecords = aRecords;
  704. _aaaaRecords = aaaaRecords;
  705. }
  706. _allowed = jsonGroup.ReadArrayAsMap("allowed", GetMapEntry);
  707. _blocked = jsonGroup.ReadArrayAsMap("blocked", GetMapEntry);
  708. _allowListUrls = jsonGroup.ReadArray("allowListUrls", GetUriEntry);
  709. _blockListUrls = jsonGroup.ReadArray("blockListUrls", GetUrlEntry);
  710. _allowedRegex = jsonGroup.ReadArray("allowedRegex", GetRegexEntry);
  711. _blockedRegex = jsonGroup.ReadArray("blockedRegex", GetRegexEntry);
  712. _regexAllowListUrls = jsonGroup.ReadArray("regexAllowListUrls", GetUriEntry);
  713. _regexBlockListUrls = jsonGroup.ReadArray("regexBlockListUrls", GetUrlEntry);
  714. _adblockListUrls = jsonGroup.ReadArray("adblockListUrls", GetUrlEntry);
  715. }
  716. #endregion
  717. #region private
  718. private static Tuple<string, object> GetMapEntry(JsonElement jsonElement)
  719. {
  720. return new Tuple<string, object>(jsonElement.GetString(), null);
  721. }
  722. private static Uri GetUriEntry(string uriString)
  723. {
  724. return new Uri(uriString);
  725. }
  726. private UrlEntry GetUrlEntry(JsonElement jsonUrl)
  727. {
  728. return new UrlEntry(jsonUrl, this);
  729. }
  730. private static Regex GetRegexEntry(string pattern)
  731. {
  732. return new Regex(pattern, RegexOptions.IgnoreCase | RegexOptions.Singleline | RegexOptions.Compiled);
  733. }
  734. #endregion
  735. #region public
  736. public void LoadListZones()
  737. {
  738. {
  739. Dictionary<Uri, BlockList> allowListZones = new Dictionary<Uri, BlockList>(_allowListUrls.Length);
  740. foreach (Uri listUrl in _allowListUrls)
  741. {
  742. if (_app._allAllowListZones.TryGetValue(listUrl, out BlockList allowListZone))
  743. allowListZones.Add(listUrl, allowListZone);
  744. }
  745. _allowListZones = allowListZones;
  746. }
  747. {
  748. Dictionary<Uri, ListZoneEntry<BlockList>> blockListZones = new Dictionary<Uri, ListZoneEntry<BlockList>>(_blockListUrls.Length);
  749. foreach (UrlEntry listUrl in _blockListUrls)
  750. {
  751. if (_app._allBlockListZones.TryGetValue(listUrl.Uri, out BlockList blockListZone))
  752. blockListZones.Add(listUrl.Uri, new ListZoneEntry<BlockList>(listUrl, blockListZone));
  753. }
  754. _blockListZones = blockListZones;
  755. }
  756. {
  757. Dictionary<Uri, RegexList> regexAllowListZones = new Dictionary<Uri, RegexList>(_regexAllowListUrls.Length);
  758. foreach (Uri listUrl in _regexAllowListUrls)
  759. {
  760. if (_app._allRegexAllowListZones.TryGetValue(listUrl, out RegexList regexAllowListZone))
  761. regexAllowListZones.Add(listUrl, regexAllowListZone);
  762. }
  763. _regexAllowListZones = regexAllowListZones;
  764. }
  765. {
  766. Dictionary<Uri, ListZoneEntry<RegexList>> regexBlockListZones = new Dictionary<Uri, ListZoneEntry<RegexList>>(_regexBlockListUrls.Length);
  767. foreach (UrlEntry listUrl in _regexBlockListUrls)
  768. {
  769. if (_app._allRegexBlockListZones.TryGetValue(listUrl.Uri, out RegexList regexBlockListZone))
  770. regexBlockListZones.Add(listUrl.Uri, new ListZoneEntry<RegexList>(listUrl, regexBlockListZone));
  771. }
  772. _regexBlockListZones = regexBlockListZones;
  773. }
  774. {
  775. Dictionary<Uri, ListZoneEntry<AdBlockList>> adBlockListZones = new Dictionary<Uri, ListZoneEntry<AdBlockList>>(_adblockListUrls.Length);
  776. foreach (UrlEntry listUrl in _adblockListUrls)
  777. {
  778. if (_app._allAdBlockListZones.TryGetValue(listUrl.Uri, out AdBlockList adBlockListZone))
  779. adBlockListZones.Add(listUrl.Uri, new ListZoneEntry<AdBlockList>(listUrl, adBlockListZone));
  780. }
  781. _adBlockListZones = adBlockListZones;
  782. }
  783. }
  784. public bool IsZoneAllowed(string domain)
  785. {
  786. domain = domain.ToLower();
  787. //allowed, allow list zone, allowedRegex, regex allow list zone, adblock list zone
  788. return IsZoneFound(_allowed, domain, out _) || IsZoneFound(_allowListZones, domain, out _, out _) || IsMatchFound(_allowedRegex, domain, out _) || IsMatchFound(_regexAllowListZones, domain, out _, out _) || App.IsZoneAllowed(_adBlockListZones, domain, out _, out _);
  789. }
  790. public bool IsZoneBlocked(string domain, out string blockedDomain, out string blockedRegex, out UrlEntry listUrl)
  791. {
  792. domain = domain.ToLower();
  793. //blocked
  794. if (IsZoneFound(_blocked, domain, out string foundZone1))
  795. {
  796. //found zone blocked
  797. blockedDomain = foundZone1;
  798. blockedRegex = null;
  799. listUrl = new UrlEntry(null, this);
  800. return true;
  801. }
  802. //block list zone
  803. if (IsZoneFound(_blockListZones, domain, out string foundZone2, out UrlEntry blockListUrl1))
  804. {
  805. //found zone blocked
  806. blockedDomain = foundZone2;
  807. blockedRegex = null;
  808. listUrl = blockListUrl1;
  809. return true;
  810. }
  811. //blockedRegex
  812. if (IsMatchFound(_blockedRegex, domain, out string blockedPattern1))
  813. {
  814. //found pattern blocked
  815. blockedDomain = null;
  816. blockedRegex = blockedPattern1;
  817. listUrl = new UrlEntry(null, this);
  818. return true;
  819. }
  820. //regex block list zone
  821. if (IsMatchFound(_regexBlockListZones, domain, out string blockedPattern2, out UrlEntry blockListUrl2))
  822. {
  823. //found pattern blocked
  824. blockedDomain = null;
  825. blockedRegex = blockedPattern2;
  826. listUrl = blockListUrl2;
  827. return true;
  828. }
  829. //adblock list zone
  830. if (App.IsZoneBlocked(_adBlockListZones, domain, out string foundZone3, out UrlEntry blockListUrl3))
  831. {
  832. //found zone blocked
  833. blockedDomain = foundZone3;
  834. blockedRegex = null;
  835. listUrl = blockListUrl3;
  836. return true;
  837. }
  838. blockedDomain = null;
  839. blockedRegex = null;
  840. listUrl = null;
  841. return false;
  842. }
  843. #endregion
  844. #region properties
  845. public string Name
  846. { get { return _name; } }
  847. public bool EnableBlocking
  848. { get { return _enableBlocking; } }
  849. public bool AllowTxtBlockingReport
  850. { get { return _allowTxtBlockingReport; } }
  851. public bool BlockAsNxDomain
  852. { get { return _blockAsNxDomain; } }
  853. public List<DnsARecordData> ARecords
  854. { get { return _aRecords; } }
  855. public List<DnsAAAARecordData> AAAARecords
  856. { get { return _aaaaRecords; } }
  857. public Uri[] AllowListUrls
  858. { get { return _allowListUrls; } }
  859. public UrlEntry[] BlockListUrls
  860. { get { return _blockListUrls; } }
  861. public UrlEntry[] RegexBlockListUrls
  862. { get { return _regexBlockListUrls; } }
  863. public Uri[] RegexAllowListUrls
  864. { get { return _regexAllowListUrls; } }
  865. public UrlEntry[] AdblockListUrls
  866. { get { return _adblockListUrls; } }
  867. #endregion
  868. }
  869. abstract class ListBase
  870. {
  871. #region variables
  872. protected readonly IDnsServer _dnsServer;
  873. protected readonly Uri _listUrl;
  874. protected readonly bool _isAllowList;
  875. protected readonly bool _isRegexList;
  876. protected readonly bool _isAdblockList;
  877. protected readonly string _listFilePath;
  878. bool _listZoneLoaded;
  879. DateTime _lastModified;
  880. volatile bool _isLoading;
  881. #endregion
  882. #region constructor
  883. public ListBase(IDnsServer dnsServer, Uri listUrl, bool isAllowList, bool isRegexList, bool isAdblockList)
  884. {
  885. _dnsServer = dnsServer;
  886. _listUrl = listUrl;
  887. _isAllowList = isAllowList;
  888. _isRegexList = isRegexList;
  889. _isAdblockList = isAdblockList;
  890. _listFilePath = Path.Combine(Path.Combine(_dnsServer.ApplicationFolder, "blocklists"), Convert.ToHexString(SHA256.HashData(Encoding.UTF8.GetBytes(_listUrl.AbsoluteUri))).ToLower());
  891. }
  892. #endregion
  893. #region private
  894. private async Task<bool> DownloadListFileAsync()
  895. {
  896. try
  897. {
  898. _dnsServer.WriteLog("Advanced Blocking app is downloading " + (_isAdblockList ? "adblock" : (_isRegexList ? "regex " : "") + (_isAllowList ? "allow" : "block")) + " list: " + _listUrl.AbsoluteUri);
  899. if (_listUrl.IsFile)
  900. {
  901. if (File.Exists(_listFilePath))
  902. {
  903. if (File.GetLastWriteTimeUtc(_listUrl.LocalPath) <= File.GetLastWriteTimeUtc(_listFilePath))
  904. {
  905. _dnsServer.WriteLog("Advanced Blocking app successfully checked for a new update of the " + (_isAdblockList ? "adblock" : (_isRegexList ? "regex " : "") + (_isAllowList ? "allow" : "block")) + " list: " + _listUrl.AbsoluteUri);
  906. return false;
  907. }
  908. }
  909. File.Copy(_listUrl.LocalPath, _listFilePath, true);
  910. _lastModified = File.GetLastWriteTimeUtc(_listFilePath);
  911. _dnsServer.WriteLog("Advanced Blocking app successfully downloaded " + (_isAdblockList ? "adblock" : (_isRegexList ? "regex " : "") + (_isAllowList ? "allow" : "block")) + " list (" + WebUtilities.GetFormattedSize(new FileInfo(_listFilePath).Length) + "): " + _listUrl.AbsoluteUri);
  912. return true;
  913. }
  914. else
  915. {
  916. SocketsHttpHandler handler = new SocketsHttpHandler();
  917. handler.Proxy = _dnsServer.Proxy;
  918. handler.UseProxy = _dnsServer.Proxy is not null;
  919. handler.AutomaticDecompression = DecompressionMethods.All;
  920. using (HttpClient http = new HttpClient(new HttpClientNetworkHandler(handler, _dnsServer.PreferIPv6 ? HttpClientNetworkType.PreferIPv6 : HttpClientNetworkType.Default, _dnsServer)))
  921. {
  922. if (File.Exists(_listFilePath))
  923. http.DefaultRequestHeaders.IfModifiedSince = File.GetLastWriteTimeUtc(_listFilePath);
  924. HttpResponseMessage httpResponse = await http.GetAsync(_listUrl);
  925. switch (httpResponse.StatusCode)
  926. {
  927. case HttpStatusCode.OK:
  928. string listDownloadFilePath = _listFilePath + ".downloading";
  929. using (FileStream fS = new FileStream(listDownloadFilePath, FileMode.Create, FileAccess.Write))
  930. {
  931. using (Stream httpStream = await httpResponse.Content.ReadAsStreamAsync())
  932. {
  933. await httpStream.CopyToAsync(fS);
  934. }
  935. }
  936. File.Move(listDownloadFilePath, _listFilePath, true);
  937. if (httpResponse.Content.Headers.LastModified is null)
  938. {
  939. _lastModified = DateTime.UtcNow;
  940. }
  941. else
  942. {
  943. _lastModified = httpResponse.Content.Headers.LastModified.Value.UtcDateTime;
  944. File.SetLastWriteTimeUtc(_listFilePath, _lastModified);
  945. }
  946. _dnsServer.WriteLog("Advanced Blocking app successfully downloaded " + (_isAdblockList ? "adblock" : (_isRegexList ? "regex " : "") + (_isAllowList ? "allow" : "block")) + " list (" + WebUtilities.GetFormattedSize(new FileInfo(_listFilePath).Length) + "): " + _listUrl.AbsoluteUri);
  947. return true;
  948. case HttpStatusCode.NotModified:
  949. _dnsServer.WriteLog("Advanced Blocking app successfully checked for a new update of the " + (_isAdblockList ? "adblock" : (_isRegexList ? "regex " : "") + (_isAllowList ? "allow" : "block")) + " list: " + _listUrl.AbsoluteUri);
  950. return false;
  951. default:
  952. throw new HttpRequestException((int)httpResponse.StatusCode + " " + httpResponse.ReasonPhrase);
  953. }
  954. }
  955. }
  956. }
  957. catch (Exception ex)
  958. {
  959. _dnsServer.WriteLog("Advanced Blocking app failed to download " + (_isAdblockList ? "adblock" : (_isRegexList ? "regex " : "") + (_isAllowList ? "allow" : "block")) + " list and will use previously downloaded file (if available): " + _listUrl.AbsoluteUri + "\r\n" + ex.ToString());
  960. return false;
  961. }
  962. }
  963. #endregion
  964. #region protected
  965. protected abstract void LoadListZone();
  966. #endregion
  967. #region public
  968. public async Task LoadAsync()
  969. {
  970. if (_isLoading)
  971. return;
  972. _isLoading = true;
  973. try
  974. {
  975. if (File.Exists(_listFilePath))
  976. {
  977. _lastModified = File.GetLastWriteTimeUtc(_listFilePath);
  978. if (_listUrl.IsFile && (File.GetLastWriteTimeUtc(_listUrl.LocalPath) > _lastModified))
  979. {
  980. File.Copy(_listUrl.LocalPath, _listFilePath, true);
  981. _lastModified = File.GetLastWriteTimeUtc(_listFilePath);
  982. _dnsServer.WriteLog("Advanced Blocking app successfully downloaded " + (_isAdblockList ? "adblock" : (_isRegexList ? "regex " : "") + (_isAllowList ? "allow" : "block")) + " list (" + WebUtilities.GetFormattedSize(new FileInfo(_listFilePath).Length) + "): " + _listUrl.AbsoluteUri);
  983. LoadListZone();
  984. _listZoneLoaded = true;
  985. }
  986. else if (!_listZoneLoaded)
  987. {
  988. LoadListZone();
  989. _listZoneLoaded = true;
  990. }
  991. }
  992. else
  993. {
  994. if (await DownloadListFileAsync())
  995. {
  996. LoadListZone();
  997. _listZoneLoaded = true;
  998. }
  999. }
  1000. }
  1001. finally
  1002. {
  1003. _isLoading = false;
  1004. }
  1005. }
  1006. public async Task<bool> UpdateAsync()
  1007. {
  1008. if (await DownloadListFileAsync())
  1009. {
  1010. LoadListZone();
  1011. return true;
  1012. }
  1013. return false;
  1014. }
  1015. #endregion
  1016. #region properties
  1017. public DateTime LastModified
  1018. { get { return _lastModified; } }
  1019. #endregion
  1020. }
  1021. class BlockList : ListBase
  1022. {
  1023. #region variables
  1024. readonly static char[] _popWordSeperator = new char[] { ' ', '\t' };
  1025. Dictionary<string, object> _listZone = new Dictionary<string, object>(0);
  1026. #endregion
  1027. #region constructor
  1028. public BlockList(IDnsServer dnsServer, Uri listUrl, bool isAllowList)
  1029. : base(dnsServer, listUrl, isAllowList, false, false)
  1030. { }
  1031. #endregion
  1032. #region private
  1033. private static string PopWord(ref string line)
  1034. {
  1035. if (line.Length == 0)
  1036. return line;
  1037. line = line.TrimStart(_popWordSeperator);
  1038. int i = line.IndexOfAny(_popWordSeperator);
  1039. string word;
  1040. if (i < 0)
  1041. {
  1042. word = line;
  1043. line = "";
  1044. }
  1045. else
  1046. {
  1047. word = line.Substring(0, i);
  1048. line = line.Substring(i + 1);
  1049. }
  1050. return word;
  1051. }
  1052. private Queue<string> ReadListFile()
  1053. {
  1054. Queue<string> domains = new Queue<string>();
  1055. try
  1056. {
  1057. _dnsServer.WriteLog("Advanced Blocking app is reading " + (_isAllowList ? "allow" : "block") + " list from: " + _listUrl.AbsoluteUri);
  1058. using (FileStream fS = new FileStream(_listFilePath, FileMode.Open, FileAccess.Read))
  1059. {
  1060. //parse hosts file and populate block zone
  1061. StreamReader sR = new StreamReader(fS, true);
  1062. char[] trimSeperator = new char[] { ' ', '\t', '*', '.' };
  1063. string line;
  1064. string firstWord;
  1065. string secondWord;
  1066. string hostname;
  1067. while (true)
  1068. {
  1069. line = sR.ReadLine();
  1070. if (line == null)
  1071. break; //eof
  1072. line = line.TrimStart(trimSeperator);
  1073. if (line.Length == 0)
  1074. continue; //skip empty line
  1075. if (line.StartsWith('#'))
  1076. continue; //skip comment line
  1077. firstWord = PopWord(ref line);
  1078. if (line.Length == 0)
  1079. {
  1080. hostname = firstWord;
  1081. }
  1082. else
  1083. {
  1084. secondWord = PopWord(ref line);
  1085. if ((secondWord.Length == 0) || secondWord.StartsWith('#'))
  1086. hostname = firstWord;
  1087. else
  1088. hostname = secondWord;
  1089. }
  1090. hostname = hostname.Trim('.').ToLower();
  1091. switch (hostname)
  1092. {
  1093. case "":
  1094. case "localhost":
  1095. case "localhost.localdomain":
  1096. case "local":
  1097. case "broadcasthost":
  1098. case "ip6-localhost":
  1099. case "ip6-loopback":
  1100. case "ip6-localnet":
  1101. case "ip6-mcastprefix":
  1102. case "ip6-allnodes":
  1103. case "ip6-allrouters":
  1104. case "ip6-allhosts":
  1105. continue; //skip these hostnames
  1106. }
  1107. if (!DnsClient.IsDomainNameValid(hostname))
  1108. continue;
  1109. if (IPAddress.TryParse(hostname, out _))
  1110. continue; //skip line when hostname is IP address
  1111. domains.Enqueue(hostname);
  1112. }
  1113. }
  1114. _dnsServer.WriteLog("Advanced Blocking app read " + (_isAllowList ? "allow" : "block") + " list file (" + domains.Count + " domains) from: " + _listUrl.AbsoluteUri);
  1115. }
  1116. catch (Exception ex)
  1117. {
  1118. _dnsServer.WriteLog("Advanced Blocking app failed to read " + (_isAllowList ? "allow" : "block") + " list from: " + _listUrl.AbsoluteUri + "\r\n" + ex.ToString());
  1119. }
  1120. return domains;
  1121. }
  1122. #endregion
  1123. #region protected
  1124. protected override void LoadListZone()
  1125. {
  1126. Queue<string> listQueue = ReadListFile();
  1127. Dictionary<string, object> listZone = new Dictionary<string, object>(listQueue.Count);
  1128. while (listQueue.Count > 0)
  1129. listZone.TryAdd(listQueue.Dequeue(), null);
  1130. _listZone = listZone;
  1131. }
  1132. #endregion
  1133. #region public
  1134. public bool IsZoneFound(string domain, out string foundZone)
  1135. {
  1136. return App.IsZoneFound(_listZone, domain, out foundZone);
  1137. }
  1138. #endregion
  1139. }
  1140. class RegexList : ListBase
  1141. {
  1142. #region variables
  1143. Regex[] _regexListZone = Array.Empty<Regex>();
  1144. #endregion
  1145. #region constructor
  1146. public RegexList(IDnsServer dnsServer, Uri listUrl, bool isAllowList)
  1147. : base(dnsServer, listUrl, isAllowList, true, false)
  1148. { }
  1149. #endregion
  1150. #region private
  1151. private Queue<string> ReadRegexListFile()
  1152. {
  1153. Queue<string> regices = new Queue<string>();
  1154. try
  1155. {
  1156. _dnsServer.WriteLog("Advanced Blocking app is reading regex " + (_isAllowList ? "allow" : "block") + " list from: " + _listUrl.AbsoluteUri);
  1157. using (FileStream fS = new FileStream(_listFilePath, FileMode.Open, FileAccess.Read))
  1158. {
  1159. //parse hosts file and populate block zone
  1160. StreamReader sR = new StreamReader(fS, true);
  1161. char[] trimSeperator = new char[] { ' ', '\t' };
  1162. string line;
  1163. while (true)
  1164. {
  1165. line = sR.ReadLine();
  1166. if (line == null)
  1167. break; //eof
  1168. line = line.TrimStart(trimSeperator);
  1169. if (line.Length == 0)
  1170. continue; //skip empty line
  1171. if (line.StartsWith('#'))
  1172. continue; //skip comment line
  1173. regices.Enqueue(line);
  1174. }
  1175. }
  1176. _dnsServer.WriteLog("Advanced Blocking app read regex " + (_isAllowList ? "allow" : "block") + " list file (" + regices.Count + " regex patterns) from: " + _listUrl.AbsoluteUri);
  1177. }
  1178. catch (Exception ex)
  1179. {
  1180. _dnsServer.WriteLog("Advanced Blocking app failed to read regex " + (_isAllowList ? "allow" : "block") + " list from: " + _listUrl.AbsoluteUri + "\r\n" + ex.ToString());
  1181. }
  1182. return regices;
  1183. }
  1184. #endregion
  1185. #region protected
  1186. protected override void LoadListZone()
  1187. {
  1188. Queue<string> regexPatterns = ReadRegexListFile();
  1189. List<Regex> regexListZone = new List<Regex>(regexPatterns.Count);
  1190. while (regexPatterns.Count > 0)
  1191. {
  1192. try
  1193. {
  1194. regexListZone.Add(new Regex(regexPatterns.Dequeue(), RegexOptions.IgnoreCase | RegexOptions.Singleline | RegexOptions.Compiled));
  1195. }
  1196. catch (RegexParseException ex)
  1197. {
  1198. _dnsServer.WriteLog(ex);
  1199. }
  1200. }
  1201. _regexListZone = regexListZone.ToArray();
  1202. }
  1203. #endregion
  1204. #region public
  1205. public bool IsMatchFound(string domain, out string matchingPattern)
  1206. {
  1207. return App.IsMatchFound(_regexListZone, domain, out matchingPattern);
  1208. }
  1209. #endregion
  1210. }
  1211. class AdBlockList : ListBase
  1212. {
  1213. #region variables
  1214. Dictionary<string, object> _allowedListZone = new Dictionary<string, object>(0);
  1215. Dictionary<string, object> _blockedListZone = new Dictionary<string, object>(0);
  1216. #endregion
  1217. #region constructor
  1218. public AdBlockList(IDnsServer dnsServer, Uri listUrl)
  1219. : base(dnsServer, listUrl, false, false, true)
  1220. { }
  1221. #endregion
  1222. #region private
  1223. private void ReadAdblockListFile(out Queue<string> allowedDomains, out Queue<string> blockedDomains)
  1224. {
  1225. allowedDomains = new Queue<string>();
  1226. blockedDomains = new Queue<string>();
  1227. try
  1228. {
  1229. _dnsServer.WriteLog("Advanced Blocking app is reading adblock list from: " + _listUrl.AbsoluteUri);
  1230. using (FileStream fS = new FileStream(_listFilePath, FileMode.Open, FileAccess.Read))
  1231. {
  1232. //parse hosts file and populate block zone
  1233. StreamReader sR = new StreamReader(fS, true);
  1234. char[] trimSeperator = new char[] { ' ', '\t' };
  1235. string line;
  1236. while (true)
  1237. {
  1238. line = sR.ReadLine();
  1239. if (line == null)
  1240. break; //eof
  1241. line = line.TrimStart(trimSeperator);
  1242. if (line.Length == 0)
  1243. continue; //skip empty line
  1244. if (line.StartsWith('!'))
  1245. continue; //skip comment line
  1246. if (line.StartsWith("||"))
  1247. {
  1248. int i = line.IndexOf('^');
  1249. if (i > -1)
  1250. {
  1251. string domain = line.Substring(2, i - 2);
  1252. string options = line.Substring(i + 1);
  1253. if (((options.Length == 0) || (options.StartsWith('$') && (options.Contains("doc") || options.Contains("all")))) && DnsClient.IsDomainNameValid(domain))
  1254. blockedDomains.Enqueue(domain);
  1255. }
  1256. else
  1257. {
  1258. string domain = line.Substring(2);
  1259. if (DnsClient.IsDomainNameValid(domain))
  1260. blockedDomains.Enqueue(domain);
  1261. }
  1262. }
  1263. else if (line.StartsWith("@@||"))
  1264. {
  1265. int i = line.IndexOf('^');
  1266. if (i > -1)
  1267. {
  1268. string domain = line.Substring(4, i - 4);
  1269. string options = line.Substring(i + 1);
  1270. if (((options.Length == 0) || (options.StartsWith('$') && (options.Contains("doc") || options.Contains("all")))) && DnsClient.IsDomainNameValid(domain))
  1271. allowedDomains.Enqueue(domain);
  1272. }
  1273. else
  1274. {
  1275. string domain = line.Substring(4);
  1276. if (DnsClient.IsDomainNameValid(domain))
  1277. allowedDomains.Enqueue(domain);
  1278. }
  1279. }
  1280. }
  1281. }
  1282. _dnsServer.WriteLog("Advanced Blocking app read adblock list file (" + (allowedDomains.Count + blockedDomains.Count) + " domains) from: " + _listUrl.AbsoluteUri);
  1283. }
  1284. catch (Exception ex)
  1285. {
  1286. _dnsServer.WriteLog("Advanced Blocking app failed to read adblock list from: " + _listUrl.AbsoluteUri + "\r\n" + ex.ToString());
  1287. }
  1288. }
  1289. #endregion
  1290. #region protected
  1291. protected override void LoadListZone()
  1292. {
  1293. ReadAdblockListFile(out Queue<string> allowedDomains, out Queue<string> blockedDomains);
  1294. Dictionary<string, object> allowedListZone = new Dictionary<string, object>(allowedDomains.Count);
  1295. Dictionary<string, object> blockedListZone = new Dictionary<string, object>(blockedDomains.Count);
  1296. while (allowedDomains.Count > 0)
  1297. allowedListZone.TryAdd(allowedDomains.Dequeue(), null);
  1298. while (blockedDomains.Count > 0)
  1299. blockedListZone.TryAdd(blockedDomains.Dequeue(), null);
  1300. _allowedListZone = allowedListZone;
  1301. _blockedListZone = blockedListZone;
  1302. }
  1303. #endregion
  1304. #region public
  1305. public bool IsZoneAllowed(string domain, out string foundZone)
  1306. {
  1307. return IsZoneFound(_allowedListZone, domain, out foundZone);
  1308. }
  1309. public bool IsZoneBlocked(string domain, out string foundZone)
  1310. {
  1311. return IsZoneFound(_blockedListZone, domain, out foundZone);
  1312. }
  1313. #endregion
  1314. }
  1315. }
  1316. }