/* Technitium DNS Server Copyright (C) 2024 Shreyas Zare (shreyas@technitium.com) This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . */ using DnsServerCore.Dns.Zones; using System; using System.Collections.Generic; using System.IO; using System.Text; using System.Threading; using TechnitiumLibrary.IO; using TechnitiumLibrary.Net.Dns; using TechnitiumLibrary.Net.Dns.ResourceRecords; namespace DnsServerCore.Dns.ZoneManagers { public sealed class BlockedZoneManager { #region variables readonly DnsServer _dnsServer; readonly AuthZoneManager _zoneManager; DnsSOARecordData _soaRecord; DnsNSRecordData _nsRecord; readonly object _saveLock = new object(); bool _pendingSave; readonly Timer _saveTimer; const int SAVE_TIMER_INITIAL_INTERVAL = 10000; #endregion #region constructor public BlockedZoneManager(DnsServer dnsServer) { _dnsServer = dnsServer; _zoneManager = new AuthZoneManager(_dnsServer); UpdateServerDomain(); _saveTimer = new Timer(delegate (object state) { lock (_saveLock) { if (_pendingSave) { try { SaveZoneFileInternal(); _pendingSave = false; } catch (Exception ex) { _dnsServer.LogManager.Write(ex); //set timer to retry again _saveTimer.Change(SAVE_TIMER_INITIAL_INTERVAL, Timeout.Infinite); } } } }); } #endregion #region IDisposable bool _disposed; public void Dispose() { if (_disposed) return; lock (_saveLock) { _saveTimer?.Dispose(); if (_pendingSave) { try { SaveZoneFileInternal(); } catch (Exception ex) { _dnsServer.LogManager.Write(ex); } finally { _pendingSave = false; } } } _disposed = true; } #endregion #region private internal void UpdateServerDomain() { _soaRecord = new DnsSOARecordData(_dnsServer.ServerDomain, _dnsServer.ResponsiblePerson.Address, 1, 14400, 3600, 604800, _dnsServer.BlockingAnswerTtl); _nsRecord = new DnsNSRecordData(_dnsServer.ServerDomain); _zoneManager.UpdateServerDomain(true); } private void SaveZoneFileInternal() { IReadOnlyList blockedZones = _dnsServer.BlockedZoneManager.GetAllZones(); string blockedZoneFile = Path.Combine(_dnsServer.ConfigFolder, "blocked.config"); using (FileStream fS = new FileStream(blockedZoneFile, FileMode.Create, FileAccess.Write)) { BinaryWriter bW = new BinaryWriter(fS); bW.Write(Encoding.ASCII.GetBytes("BZ")); //format bW.Write((byte)1); //version bW.Write(blockedZones.Count); foreach (AuthZoneInfo zone in blockedZones) bW.WriteShortString(zone.Name); } _dnsServer.LogManager?.Write("DNS Server blocked zone file was saved: " + blockedZoneFile); } #endregion #region public public void LoadBlockedZoneFile() { _zoneManager.Flush(); string blockedZoneFile = Path.Combine(_dnsServer.ConfigFolder, "blocked.config"); try { string oldCustomBlockedZoneFile = Path.Combine(_dnsServer.ConfigFolder, "custom-blocked.config"); if (File.Exists(oldCustomBlockedZoneFile)) { if (File.Exists(blockedZoneFile)) File.Delete(blockedZoneFile); File.Move(oldCustomBlockedZoneFile, blockedZoneFile); } } catch (Exception ex) { _dnsServer.LogManager?.Write(ex); } try { _dnsServer.LogManager?.Write("DNS Server is loading blocked zone file: " + blockedZoneFile); using (FileStream fS = new FileStream(blockedZoneFile, FileMode.Open, FileAccess.Read)) { BinaryReader bR = new BinaryReader(fS); if (Encoding.ASCII.GetString(bR.ReadBytes(2)) != "BZ") //format throw new InvalidDataException("DnsServer blocked zone file format is invalid."); byte version = bR.ReadByte(); switch (version) { case 1: int length = bR.ReadInt32(); int i = 0; _zoneManager.LoadSpecialPrimaryZones(delegate () { if (i++ < length) return bR.ReadShortString(); return null; }, _soaRecord, _nsRecord); break; default: throw new InvalidDataException("DnsServer blocked zone file version not supported."); } } _dnsServer.LogManager?.Write("DNS Server blocked zone file was loaded: " + blockedZoneFile); } catch (FileNotFoundException) { } catch (Exception ex) { _dnsServer.LogManager?.Write("DNS Server encountered an error while loading blocked zone file: " + blockedZoneFile + "\r\n" + ex.ToString()); } } public void ImportZones(string[] domains) { _zoneManager.LoadSpecialPrimaryZones(domains, _soaRecord, _nsRecord); } public bool BlockZone(string domain) { if (_zoneManager.CreateSpecialPrimaryZone(domain, _soaRecord, _nsRecord) != null) return true; return false; } public bool DeleteZone(string domain) { if (_zoneManager.DeleteZone(domain)) return true; return false; } public void Flush() { _zoneManager.Flush(); } public IReadOnlyList GetAllZones() { return _zoneManager.GetAllZones(); } public void ListAllRecords(string domain, List records) { _zoneManager.ListAllRecords(domain, domain, records); } public void ListSubDomains(string domain, List subDomains) { _zoneManager.ListSubDomains(domain, subDomains); } public void SaveZoneFile() { lock (_saveLock) { if (_pendingSave) return; _pendingSave = true; _saveTimer.Change(SAVE_TIMER_INITIAL_INTERVAL, Timeout.Infinite); } } public DnsDatagram Query(DnsDatagram request) { if (_zoneManager.TotalZones < 1) return null; return _zoneManager.Query(request, false); } #endregion #region properties internal DnsSOARecordData DnsSOARecord { get { return _soaRecord; } } public int TotalZonesBlocked { get { return _zoneManager.TotalZones; } } #endregion } }