App.cs 56 KB

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