/* Technitium DNS Server Copyright (C) 2021 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 DnsApplicationCommon; using DnsServerCore.Dhcp; using DnsServerCore.Dhcp.Options; using DnsServerCore.Dns; using DnsServerCore.Dns.Applications; using DnsServerCore.Dns.ResourceRecords; using DnsServerCore.Dns.ZoneManagers; using DnsServerCore.Dns.Zones; using Newtonsoft.Json; using Newtonsoft.Json.Converters; using System; using System.Collections.Concurrent; using System.Collections.Generic; using System.Globalization; using System.IO; using System.IO.Compression; using System.Net; using System.Net.Http; using System.Net.Security; using System.Net.Sockets; using System.Reflection; using System.Security.Cryptography; using System.Security.Cryptography.X509Certificates; using System.Text; using System.Threading; using System.Threading.Tasks; using TechnitiumLibrary.IO; using TechnitiumLibrary.Net; using TechnitiumLibrary.Net.Dns; using TechnitiumLibrary.Net.Dns.ResourceRecords; using TechnitiumLibrary.Net.Http; using TechnitiumLibrary.Net.Proxy; namespace DnsServerCore { public sealed class DnsWebService : IDisposable { #region enum enum ServiceState { Stopped = 0, Starting = 1, Running = 2, Stopping = 3 } #endregion #region variables readonly static RandomNumberGenerator _rng = RandomNumberGenerator.Create(); readonly Version _currentVersion; readonly string _appFolder; readonly string _configFolder; readonly Uri _updateCheckUri; readonly Uri _appStoreUri; readonly LogManager _log; DnsServer _dnsServer; DhcpServer _dhcpServer; IReadOnlyList _webServiceLocalAddresses = new IPAddress[] { IPAddress.Any, IPAddress.IPv6Any }; int _webServiceHttpPort = 5380; int _webServiceTlsPort = 53443; bool _webServiceEnableTls; bool _webServiceHttpToTlsRedirect; string _webServiceTlsCertificatePath; string _webServiceTlsCertificatePassword; DateTime _webServiceTlsCertificateLastModifiedOn; HttpListener _webService; IReadOnlyList _webServiceTlsListeners; X509Certificate2 _webServiceTlsCertificate; readonly IndependentTaskScheduler _webServiceTaskScheduler = new IndependentTaskScheduler(ThreadPriority.AboveNormal); string _webServiceHostname; IPEndPoint _webServiceHttpEP; string _dnsTlsCertificatePath; string _dnsTlsCertificatePassword; DateTime _dnsTlsCertificateLastModifiedOn; Timer _tlsCertificateUpdateTimer; const int TLS_CERTIFICATE_UPDATE_TIMER_INITIAL_INTERVAL = 60000; const int TLS_CERTIFICATE_UPDATE_TIMER_INTERVAL = 60000; const int MAX_LOGIN_ATTEMPTS = 5; const int BLOCK_ADDRESS_INTERVAL = 5 * 60 * 1000; readonly ConcurrentDictionary _failedLoginAttempts = new ConcurrentDictionary(); readonly ConcurrentDictionary _blockedAddresses = new ConcurrentDictionary(); readonly ConcurrentDictionary _credentials = new ConcurrentDictionary(); readonly ConcurrentDictionary _sessions = new ConcurrentDictionary(); volatile ServiceState _state = ServiceState.Stopped; Timer _blockListUpdateTimer; DateTime _blockListLastUpdatedOn; int _blockListUpdateIntervalHours = 24; const int BLOCK_LIST_UPDATE_TIMER_INITIAL_INTERVAL = 5000; const int BLOCK_LIST_UPDATE_TIMER_INTERVAL = 900000; Timer _temporaryDisableBlockingTimer; DateTime _temporaryDisableBlockingTill; List _configDisabledZones; #endregion #region constructor public DnsWebService(string configFolder = null, Uri updateCheckUri = null, Uri appStoreUri = null) { Assembly assembly = Assembly.GetExecutingAssembly(); AssemblyName assemblyName = assembly.GetName(); _currentVersion = assemblyName.Version; _appFolder = Path.GetDirectoryName(assembly.Location); if (configFolder == null) _configFolder = Path.Combine(_appFolder, "config"); else _configFolder = configFolder; if (!Directory.Exists(_configFolder)) Directory.CreateDirectory(_configFolder); _updateCheckUri = updateCheckUri; _appStoreUri = appStoreUri; _log = new LogManager(_configFolder); string blockListsFolder = Path.Combine(_configFolder, "blocklists"); if (!Directory.Exists(blockListsFolder)) Directory.CreateDirectory(blockListsFolder); } #endregion #region IDisposable private bool _disposed; private void Dispose(bool disposing) { if (_disposed) return; if (disposing) { Stop(); if (_webService != null) _webService.Close(); if (_dnsServer != null) _dnsServer.Dispose(); if (_dhcpServer != null) _dhcpServer.Dispose(); if (_log != null) _log.Dispose(); } _disposed = true; } public void Dispose() { Dispose(true); } #endregion #region private #region web service private async Task AcceptWebRequestAsync() { try { while (true) { HttpListenerContext context = await _webService.GetContextAsync(); if ((_webServiceTlsListeners != null) && (_webServiceTlsListeners.Count > 0) && _webServiceHttpToTlsRedirect) { IPEndPoint remoteEP = context.Request.RemoteEndPoint; if ((remoteEP != null) && !IPAddress.IsLoopback(remoteEP.Address)) { string domain = _webServiceTlsCertificate.GetNameInfo(X509NameType.DnsName, false); string redirectUri = "https://" + domain + ":" + _webServiceTlsPort + context.Request.Url.PathAndQuery; context.Response.Redirect(redirectUri); context.Response.Close(); continue; } } _ = ProcessRequestAsync(context.Request, context.Response); } } catch (HttpListenerException ex) { if (ex.ErrorCode == 995) return; //web service stopping _log.Write(ex); } catch (ObjectDisposedException) { //web service stopped } catch (Exception ex) { if ((_state == ServiceState.Stopping) || (_state == ServiceState.Stopped)) return; //web service stopping _log.Write(ex); } } private async Task AcceptTlsWebRequestAsync(Socket tlsListener) { try { while (true) { Socket socket = await tlsListener.AcceptAsync(); _ = TlsToHttpTunnelAsync(socket); } } catch (SocketException ex) { if (ex.SocketErrorCode == SocketError.OperationAborted) return; //web service stopping _log.Write(ex); } catch (ObjectDisposedException) { //web service stopped } catch (Exception ex) { if ((_state == ServiceState.Stopping) || (_state == ServiceState.Stopped)) return; //web service stopping _log.Write(ex); } } private async Task TlsToHttpTunnelAsync(Socket socket) { Socket tunnel = null; try { if (_webServiceLocalAddresses.Count < 1) return; string remoteIP = (socket.RemoteEndPoint as IPEndPoint).Address.ToString(); SslStream sslStream = new SslStream(new NetworkStream(socket, true)); await sslStream.AuthenticateAsServerAsync(_webServiceTlsCertificate); tunnel = new Socket(_webServiceHttpEP.AddressFamily, SocketType.Stream, ProtocolType.Tcp); tunnel.Connect(_webServiceHttpEP); NetworkStream tunnelStream = new NetworkStream(tunnel, true); //copy tunnel to ssl _ = tunnelStream.CopyToAsync(sslStream).ContinueWith(delegate (Task prevTask) { sslStream.Dispose(); tunnelStream.Dispose(); }); //copy ssl to tunnel try { while (true) { HttpRequest httpRequest = await HttpRequest.ReadRequestAsync(sslStream); if (httpRequest == null) return; //connection closed gracefully by client //inject X-Real-IP & host header httpRequest.Headers.Add("X-Real-IP", remoteIP); httpRequest.Headers[HttpRequestHeader.Host] = "localhost:" + _webServiceHttpPort.ToString(); //relay request await tunnelStream.WriteAsync(Encoding.ASCII.GetBytes(httpRequest.HttpMethod + " " + httpRequest.RequestPathAndQuery + " " + httpRequest.Protocol + "\r\n")); await tunnelStream.WriteAsync(httpRequest.Headers.ToByteArray()); if (httpRequest.InputStream != null) await httpRequest.InputStream.CopyToAsync(tunnelStream); await tunnelStream.FlushAsync(); } } finally { sslStream.Dispose(); tunnelStream.Dispose(); } } catch (IOException) { //ignore } catch (Exception ex) { _log.Write(ex); } finally { socket.Dispose(); if (tunnel != null) tunnel.Dispose(); } } private async Task ProcessRequestAsync(HttpListenerRequest request, HttpListenerResponse response) { response.AddHeader("Server", ""); response.AddHeader("X-Robots-Tag", "noindex, nofollow"); try { Uri url = request.Url; string path = url.AbsolutePath; if (!path.StartsWith("/") || path.Contains("/../") || path.Contains("/.../")) { await SendErrorAsync(response, 404); return; } if (path.StartsWith("/api/")) { using (MemoryStream mS = new MemoryStream()) { try { JsonTextWriter jsonWriter = new JsonTextWriter(new StreamWriter(mS)); jsonWriter.WriteStartObject(); switch (path) { case "/api/login": await LoginAsync(request, jsonWriter); break; case "/api/logout": Logout(request); break; default: if (!IsSessionValid(request)) throw new InvalidTokenWebServiceException("Invalid token or session expired."); jsonWriter.WritePropertyName("response"); jsonWriter.WriteStartObject(); try { switch (path) { case "/api/changePassword": ChangePassword(request); break; case "/api/checkForUpdate": await CheckForUpdateAsync(request, jsonWriter); break; case "/api/getDnsSettings": GetDnsSettings(jsonWriter); break; case "/api/setDnsSettings": SetDnsSettings(request, jsonWriter); break; case "/api/forceUpdateBlockLists": ForceUpdateBlockLists(request); break; case "/api/temporaryDisableBlocking": TemporaryDisableBlocking(request, jsonWriter); break; case "/api/backupSettings": await BackupSettingsAsync(request, response); return; case "/api/restoreSettings": await RestoreSettingsAsync(request, jsonWriter); break; case "/api/getStats": await GetStats(request, jsonWriter); break; case "/api/getTopStats": await GetTopStats(request, jsonWriter); break; case "/api/flushDnsCache": FlushCache(request); break; case "/api/listCachedZones": ListCachedZones(request, jsonWriter); break; case "/api/deleteCachedZone": DeleteCachedZone(request); break; case "/api/listAllowedZones": ListAllowedZones(request, jsonWriter); break; case "/api/importAllowedZones": ImportAllowedZones(request); break; case "/api/exportAllowedZones": ExportAllowedZones(response); return; case "/api/deleteAllowedZone": DeleteAllowedZone(request); break; case "/api/allowZone": AllowZone(request); break; case "/api/listBlockedZones": ListBlockedZones(request, jsonWriter); break; case "/api/importBlockedZones": ImportBlockedZones(request); break; case "/api/exportBlockedZones": ExportBlockedZones(response); return; case "/api/deleteBlockedZone": DeleteBlockedZone(request); break; case "/api/blockZone": BlockZone(request); break; case "/api/listZones": ListZones(jsonWriter); break; case "/api/createZone": await CreateZoneAsync(request, jsonWriter); break; case "/api/deleteZone": DeleteZone(request); break; case "/api/enableZone": EnableZone(request); break; case "/api/disableZone": DisableZone(request); break; case "/api/zone/options/get": GetZoneOptions(request, jsonWriter); break; case "/api/zone/options/set": SetZoneOptions(request); break; case "/api/zone/resync": ResyncZone(request); break; case "/api/addRecord": AddRecord(request); break; case "/api/getRecords": GetRecords(request, jsonWriter); break; case "/api/deleteRecord": DeleteRecord(request); break; case "/api/updateRecord": UpdateRecord(request); break; case "/api/apps/list": await ListInstalledAppsAsync(jsonWriter); break; case "/api/apps/listStoreApps": await ListStoreApps(jsonWriter); break; case "/api/apps/downloadAndInstall": await DownloadAndInstallAppAsync(request); break; case "/api/apps/downloadAndUpdate": await DownloadAndUpdateAppAsync(request); break; case "/api/apps/install": await InstallAppAsync(request); break; case "/api/apps/update": await UpdateAppAsync(request); break; case "/api/apps/uninstall": UninstallApp(request); break; case "/api/apps/getConfig": await GetAppConfigAsync(request, jsonWriter); break; case "/api/apps/setConfig": await SetAppConfigAsync(request); break; case "/api/resolveQuery": await ResolveQuery(request, jsonWriter); break; case "/api/listLogs": ListLogs(jsonWriter); break; case "/api/deleteLog": DeleteLog(request); break; case "/api/deleteAllLogs": DeleteAllLogs(request); break; case "/api/deleteAllStats": DeleteAllStats(request); break; case "/api/queryLogs": await QueryLogsAsync(request, jsonWriter); break; case "/api/listDhcpScopes": ListDhcpScopes(jsonWriter); break; case "/api/listDhcpLeases": ListDhcpLeases(jsonWriter); break; case "/api/getDhcpScope": GetDhcpScope(request, jsonWriter); break; case "/api/setDhcpScope": await SetDhcpScopeAsync(request); break; case "/api/enableDhcpScope": await EnableDhcpScopeAsync(request); break; case "/api/disableDhcpScope": DisableDhcpScope(request); break; case "/api/deleteDhcpScope": DeleteDhcpScope(request); break; case "/api/convertToReservedLease": ConvertToReservedLease(request); break; case "/api/convertToDynamicLease": ConvertToDynamicLease(request); break; default: await SendErrorAsync(response, 404); return; } } finally { jsonWriter.WriteEndObject(); } break; } jsonWriter.WritePropertyName("status"); jsonWriter.WriteValue("ok"); jsonWriter.WriteEndObject(); jsonWriter.Flush(); } catch (InvalidTokenWebServiceException ex) { mS.SetLength(0); JsonTextWriter jsonWriter = new JsonTextWriter(new StreamWriter(mS)); jsonWriter.WriteStartObject(); jsonWriter.WritePropertyName("status"); jsonWriter.WriteValue("invalid-token"); jsonWriter.WritePropertyName("errorMessage"); jsonWriter.WriteValue(ex.Message); jsonWriter.WriteEndObject(); jsonWriter.Flush(); } catch (Exception ex) { _log.Write(GetRequestRemoteEndPoint(request), ex); mS.SetLength(0); JsonTextWriter jsonWriter = new JsonTextWriter(new StreamWriter(mS)); jsonWriter.WriteStartObject(); jsonWriter.WritePropertyName("status"); jsonWriter.WriteValue("error"); jsonWriter.WritePropertyName("errorMessage"); jsonWriter.WriteValue(ex.Message); jsonWriter.WritePropertyName("stackTrace"); jsonWriter.WriteValue(ex.StackTrace); if (ex.InnerException != null) { jsonWriter.WritePropertyName("innerErrorMessage"); jsonWriter.WriteValue(ex.InnerException.Message); } jsonWriter.WriteEndObject(); jsonWriter.Flush(); } response.ContentType = "application/json; charset=utf-8"; response.ContentEncoding = Encoding.UTF8; response.ContentLength64 = mS.Length; mS.Position = 0; using (Stream stream = response.OutputStream) { await mS.CopyToAsync(stream); } } } else if (path.StartsWith("/log/")) { if (!IsSessionValid(request)) { await SendErrorAsync(response, 403, "Invalid token or session expired."); return; } string[] pathParts = path.Split('/'); string logFileName = pathParts[2]; int limit = 0; string strLimit = request.QueryString["limit"]; if (!string.IsNullOrEmpty(strLimit)) limit = int.Parse(strLimit); await _log.DownloadLogAsync(request, response, logFileName, limit * 1024 * 1024); } else { if (path == "/") { path = "/index.html"; } else if ((path == "/blocklist.txt") && !IPAddress.IsLoopback(GetRequestRemoteEndPoint(request).Address)) { await SendErrorAsync(response, 403); return; } string wwwroot = Path.Combine(_appFolder, "www"); path = Path.GetFullPath(wwwroot + path.Replace('/', Path.DirectorySeparatorChar)); if (!path.StartsWith(wwwroot) || !File.Exists(path)) { await SendErrorAsync(response, 404); return; } await SendFileAsync(request, response, path); } } catch (Exception ex) { if ((_state == ServiceState.Stopping) || (_state == ServiceState.Stopped)) return; //web service stopping _log.Write(GetRequestRemoteEndPoint(request), ex); await SendError(response, ex); } } private static IPEndPoint GetRequestRemoteEndPoint(HttpListenerRequest request) { try { if (request.RemoteEndPoint == null) return new IPEndPoint(IPAddress.Any, 0); if (NetUtilities.IsPrivateIP(request.RemoteEndPoint.Address)) { string xRealIp = request.Headers["X-Real-IP"]; if (IPAddress.TryParse(xRealIp, out IPAddress address)) { //get the real IP address of the requesting client from X-Real-IP header set in nginx proxy_pass block return new IPEndPoint(address, 0); } } return request.RemoteEndPoint; } catch { return new IPEndPoint(IPAddress.Any, 0); } } public static Stream GetOutputStream(HttpListenerRequest request, HttpListenerResponse response) { string strAcceptEncoding = request.Headers["Accept-Encoding"]; if (string.IsNullOrEmpty(strAcceptEncoding)) { return response.OutputStream; } else { if (strAcceptEncoding.Contains("gzip")) { response.AddHeader("Content-Encoding", "gzip"); return new GZipStream(response.OutputStream, CompressionMode.Compress); } else if (strAcceptEncoding.Contains("deflate")) { response.AddHeader("Content-Encoding", "deflate"); return new DeflateStream(response.OutputStream, CompressionMode.Compress); } else { return response.OutputStream; } } } private static Task SendError(HttpListenerResponse response, Exception ex) { return SendErrorAsync(response, 500, ex.ToString()); } private static async Task SendErrorAsync(HttpListenerResponse response, int statusCode, string message = null) { try { string statusString = statusCode + " " + DnsServer.GetHttpStatusString((HttpStatusCode)statusCode); byte[] buffer = Encoding.UTF8.GetBytes("" + statusString + "

" + statusString + "

" + (message == null ? "" : "

" + message + "

") + ""); response.StatusCode = statusCode; response.ContentType = "text/html"; response.ContentLength64 = buffer.Length; using (Stream stream = response.OutputStream) { await stream.WriteAsync(buffer); } } catch { } } private static async Task SendFileAsync(HttpListenerRequest request, HttpListenerResponse response, string filePath) { using (FileStream fS = new FileStream(filePath, FileMode.Open, FileAccess.Read, FileShare.ReadWrite)) { response.ContentType = WebUtilities.GetContentType(filePath).MediaType; response.AddHeader("Cache-Control", "private, max-age=300"); using (Stream stream = GetOutputStream(request, response)) { try { await fS.CopyToAsync(stream); } catch (HttpListenerException) { //ignore this error } } } } #endregion #region user session private string CreateSession(string username) { string token = BinaryNumber.GenerateRandomNumber256().ToString(); if (!_sessions.TryAdd(token, new UserSession(username))) throw new DnsWebServiceException("Error while creating session. Please try again."); return token; } private UserSession GetSession(string token) { if (_sessions.TryGetValue(token, out UserSession session)) return session; return null; } private UserSession GetSession(HttpListenerRequest request) { string strToken = request.QueryString["token"]; if (string.IsNullOrEmpty(strToken)) throw new DnsWebServiceException("Parameter 'token' missing."); return GetSession(strToken); } private UserSession DeleteSession(string token) { if (_sessions.TryRemove(token, out UserSession session)) return session; return null; } private UserSession DeleteSession(HttpListenerRequest request) { string strToken = request.QueryString["token"]; if (string.IsNullOrEmpty(strToken)) throw new DnsWebServiceException("Parameter 'token' missing."); return DeleteSession(strToken); } private void FailedLoginAttempt(IPAddress address) { _failedLoginAttempts.AddOrUpdate(address, 1, delegate (IPAddress key, int attempts) { return attempts + 1; }); } private bool LoginAttemptsExceedLimit(IPAddress address, int limit) { if (!_failedLoginAttempts.TryGetValue(address, out int attempts)) return false; return attempts >= limit; } private void ResetFailedLoginAttempt(IPAddress address) { _failedLoginAttempts.TryRemove(address, out _); } private void BlockAddress(IPAddress address, int interval) { _blockedAddresses.TryAdd(address, DateTime.UtcNow.AddMilliseconds(interval)); } private bool IsAddressBlocked(IPAddress address) { if (!_blockedAddresses.TryGetValue(address, out DateTime expiry)) return false; if (expiry > DateTime.UtcNow) { return true; } else { UnblockAddress(address); ResetFailedLoginAttempt(address); return false; } } private void UnblockAddress(IPAddress address) { _blockedAddresses.TryRemove(address, out _); } #endregion #region auth api private async Task LoginAsync(HttpListenerRequest request, JsonTextWriter jsonWriter) { string strUsername = request.QueryString["user"]; if (string.IsNullOrEmpty(strUsername)) throw new DnsWebServiceException("Parameter 'user' missing."); string strPassword = request.QueryString["pass"]; if (string.IsNullOrEmpty(strPassword)) throw new DnsWebServiceException("Parameter 'pass' missing."); IPEndPoint remoteEP = GetRequestRemoteEndPoint(request); if (IsAddressBlocked(remoteEP.Address)) throw new DnsWebServiceException("Max limit of " + MAX_LOGIN_ATTEMPTS + " attempts exceeded. Access blocked for " + (BLOCK_ADDRESS_INTERVAL / 1000) + " seconds."); strUsername = strUsername.Trim().ToLower(); string strPasswordHash = GetPasswordHash(strUsername, strPassword); if (!_credentials.TryGetValue(strUsername, out string passwordHash) || (passwordHash != strPasswordHash)) { if (strPassword != "admin") //exception for default password { FailedLoginAttempt(remoteEP.Address); if (LoginAttemptsExceedLimit(remoteEP.Address, MAX_LOGIN_ATTEMPTS)) BlockAddress(remoteEP.Address, BLOCK_ADDRESS_INTERVAL); await Task.Delay(1000); } throw new DnsWebServiceException("Invalid username or password for user: " + strUsername); } ResetFailedLoginAttempt(remoteEP.Address); _log.Write(remoteEP, "[" + strUsername + "] User logged in."); string token = CreateSession(strUsername); jsonWriter.WritePropertyName("token"); jsonWriter.WriteValue(token); } private bool IsSessionValid(HttpListenerRequest request) { UserSession session = GetSession(request); if (session == null) return false; if (session.HasExpired()) { DeleteSession(request); return false; } session.UpdateLastSeen(); return true; } private void ChangePassword(HttpListenerRequest request) { string strToken = request.QueryString["token"]; if (string.IsNullOrEmpty(strToken)) throw new DnsWebServiceException("Parameter 'token' missing."); string strPassword = request.QueryString["pass"]; if (string.IsNullOrEmpty(strPassword)) throw new DnsWebServiceException("Parameter 'pass' missing."); UserSession session = GetSession(strToken); if (session == null) throw new DnsWebServiceException("User session does not exists."); SetCredentials(session.Username, strPassword); SaveConfigFile(); _log.Write(GetRequestRemoteEndPoint(request), "[" + session.Username + "] Password was changed for user."); } private void Logout(HttpListenerRequest request) { string strToken = request.QueryString["token"]; if (string.IsNullOrEmpty(strToken)) throw new DnsWebServiceException("Parameter 'token' missing."); UserSession session = DeleteSession(strToken); if (session != null) _log.Write(GetRequestRemoteEndPoint(request), "[" + session.Username + "] User logged out."); } #endregion #region update api public static void CreateUpdateInfo(Stream s, string version, string displayText, string downloadLink) { BinaryWriter bW = new BinaryWriter(s); bW.Write(Encoding.ASCII.GetBytes("DU")); //format bW.Write((byte)2); //version bW.WriteShortString(version); bW.WriteShortString(displayText); bW.WriteShortString(downloadLink); } private async Task CheckForUpdateAsync(HttpListenerRequest request, JsonTextWriter jsonWriter) { Version updateVersion = null; string displayText = null; string downloadLink = null; bool updateAvailable = false; if (_updateCheckUri != null) { try { SocketsHttpHandler handler = new SocketsHttpHandler(); handler.Proxy = _dnsServer.Proxy; using (HttpClient http = new HttpClient(handler)) { byte[] response = await http.GetByteArrayAsync(_updateCheckUri); using (MemoryStream mS = new MemoryStream(response, false)) { BinaryReader bR = new BinaryReader(mS); if (Encoding.ASCII.GetString(bR.ReadBytes(2)) != "DU") //format throw new InvalidDataException("DNS Server update info format is invalid."); switch (bR.ReadByte()) //version { case 2: updateVersion = new Version(bR.ReadShortString()); displayText = bR.ReadShortString(); downloadLink = bR.ReadShortString(); break; default: throw new InvalidDataException("DNS Server update info version not supported."); } updateAvailable = updateVersion > _currentVersion; } } _log.Write(GetRequestRemoteEndPoint(request), "Check for update was done {updateAvailable: " + updateAvailable + "; updateVersion: " + updateVersion + "; displayText: " + displayText + "; downloadLink: " + downloadLink + ";}"); } catch (Exception ex) { _log.Write(GetRequestRemoteEndPoint(request), "Check for update was done {updateAvailable: False;}\r\n" + ex.ToString()); } } jsonWriter.WritePropertyName("updateAvailable"); jsonWriter.WriteValue(updateAvailable); if (updateAvailable) { if (!string.IsNullOrEmpty(displayText)) { jsonWriter.WritePropertyName("displayText"); jsonWriter.WriteValue(displayText); } jsonWriter.WritePropertyName("downloadLink"); jsonWriter.WriteValue(downloadLink); } } private static string GetCleanVersion(Version version) { string strVersion = version.Major + "." + version.Minor; if (version.Build > 0) strVersion += "." + version.Build; if (version.Revision > 0) strVersion += "." + version.Revision; return strVersion; } #endregion #region settings api private void GetDnsSettings(JsonTextWriter jsonWriter) { jsonWriter.WritePropertyName("version"); jsonWriter.WriteValue(GetCleanVersion(_currentVersion)); jsonWriter.WritePropertyName("dnsServerDomain"); jsonWriter.WriteValue(_dnsServer.ServerDomain); jsonWriter.WritePropertyName("dnsServerLocalEndPoints"); jsonWriter.WriteStartArray(); foreach (IPEndPoint localEP in _dnsServer.LocalEndPoints) jsonWriter.WriteValue(localEP.ToString()); jsonWriter.WriteEndArray(); jsonWriter.WritePropertyName("webServiceLocalAddresses"); jsonWriter.WriteStartArray(); foreach (IPAddress localAddress in _webServiceLocalAddresses) { if (localAddress.AddressFamily == AddressFamily.InterNetworkV6) jsonWriter.WriteValue("[" + localAddress.ToString() + "]"); else jsonWriter.WriteValue(localAddress.ToString()); } jsonWriter.WriteEndArray(); jsonWriter.WritePropertyName("webServiceHttpPort"); jsonWriter.WriteValue(_webServiceHttpPort); jsonWriter.WritePropertyName("webServiceEnableTls"); jsonWriter.WriteValue(_webServiceEnableTls); jsonWriter.WritePropertyName("webServiceHttpToTlsRedirect"); jsonWriter.WriteValue(_webServiceHttpToTlsRedirect); jsonWriter.WritePropertyName("webServiceTlsPort"); jsonWriter.WriteValue(_webServiceTlsPort); jsonWriter.WritePropertyName("webServiceTlsCertificatePath"); jsonWriter.WriteValue(_webServiceTlsCertificatePath); jsonWriter.WritePropertyName("webServiceTlsCertificatePassword"); jsonWriter.WriteValue("************"); jsonWriter.WritePropertyName("enableDnsOverHttp"); jsonWriter.WriteValue(_dnsServer.EnableDnsOverHttp); jsonWriter.WritePropertyName("enableDnsOverTls"); jsonWriter.WriteValue(_dnsServer.EnableDnsOverTls); jsonWriter.WritePropertyName("enableDnsOverHttps"); jsonWriter.WriteValue(_dnsServer.EnableDnsOverHttps); jsonWriter.WritePropertyName("dnsTlsCertificatePath"); jsonWriter.WriteValue(_dnsTlsCertificatePath); jsonWriter.WritePropertyName("dnsTlsCertificatePassword"); jsonWriter.WriteValue("************"); jsonWriter.WritePropertyName("tsigKeys"); { jsonWriter.WriteStartArray(); if (_dnsServer.TsigKeys is not null) { foreach (KeyValuePair tsigKey in _dnsServer.TsigKeys) { jsonWriter.WriteStartObject(); jsonWriter.WritePropertyName("keyName"); jsonWriter.WriteValue(tsigKey.Key); jsonWriter.WritePropertyName("sharedSecret"); jsonWriter.WriteValue(tsigKey.Value.SharedSecret); jsonWriter.WritePropertyName("algorithmName"); jsonWriter.WriteValue(tsigKey.Value.AlgorithmName); jsonWriter.WriteEndObject(); } } jsonWriter.WriteEndArray(); } jsonWriter.WritePropertyName("preferIPv6"); jsonWriter.WriteValue(_dnsServer.PreferIPv6); jsonWriter.WritePropertyName("enableLogging"); jsonWriter.WriteValue(_log.EnableLogging); jsonWriter.WritePropertyName("logQueries"); jsonWriter.WriteValue(_dnsServer.QueryLogManager != null); jsonWriter.WritePropertyName("useLocalTime"); jsonWriter.WriteValue(_log.UseLocalTime); jsonWriter.WritePropertyName("logFolder"); jsonWriter.WriteValue(_log.LogFolder); jsonWriter.WritePropertyName("maxLogFileDays"); jsonWriter.WriteValue(_log.MaxLogFileDays); jsonWriter.WritePropertyName("maxStatFileDays"); jsonWriter.WriteValue(_dnsServer.StatsManager.MaxStatFileDays); jsonWriter.WritePropertyName("recursion"); jsonWriter.WriteValue(_dnsServer.Recursion.ToString()); jsonWriter.WritePropertyName("recursionDeniedNetworks"); { jsonWriter.WriteStartArray(); if (_dnsServer.RecursionDeniedNetworks is not null) { foreach (NetworkAddress networkAddress in _dnsServer.RecursionDeniedNetworks) jsonWriter.WriteValue(networkAddress.ToString()); } jsonWriter.WriteEndArray(); } jsonWriter.WritePropertyName("recursionAllowedNetworks"); { jsonWriter.WriteStartArray(); if (_dnsServer.RecursionAllowedNetworks is not null) { foreach (NetworkAddress networkAddress in _dnsServer.RecursionAllowedNetworks) jsonWriter.WriteValue(networkAddress.ToString()); } jsonWriter.WriteEndArray(); } jsonWriter.WritePropertyName("randomizeName"); jsonWriter.WriteValue(_dnsServer.RandomizeName); jsonWriter.WritePropertyName("qnameMinimization"); jsonWriter.WriteValue(_dnsServer.QnameMinimization); jsonWriter.WritePropertyName("nsRevalidation"); jsonWriter.WriteValue(_dnsServer.NsRevalidation); jsonWriter.WritePropertyName("qpmLimitRequests"); jsonWriter.WriteValue(_dnsServer.QpmLimitRequests); jsonWriter.WritePropertyName("qpmLimitErrors"); jsonWriter.WriteValue(_dnsServer.QpmLimitErrors); jsonWriter.WritePropertyName("qpmLimitSampleMinutes"); jsonWriter.WriteValue(_dnsServer.QpmLimitSampleMinutes); jsonWriter.WritePropertyName("qpmLimitIPv4PrefixLength"); jsonWriter.WriteValue(_dnsServer.QpmLimitIPv4PrefixLength); jsonWriter.WritePropertyName("qpmLimitIPv6PrefixLength"); jsonWriter.WriteValue(_dnsServer.QpmLimitIPv6PrefixLength); jsonWriter.WritePropertyName("serveStale"); jsonWriter.WriteValue(_dnsServer.ServeStale); jsonWriter.WritePropertyName("serveStaleTtl"); jsonWriter.WriteValue(_dnsServer.CacheZoneManager.ServeStaleTtl); jsonWriter.WritePropertyName("cacheMinimumRecordTtl"); jsonWriter.WriteValue(_dnsServer.CacheZoneManager.MinimumRecordTtl); jsonWriter.WritePropertyName("cacheMaximumRecordTtl"); jsonWriter.WriteValue(_dnsServer.CacheZoneManager.MaximumRecordTtl); jsonWriter.WritePropertyName("cacheNegativeRecordTtl"); jsonWriter.WriteValue(_dnsServer.CacheZoneManager.NegativeRecordTtl); jsonWriter.WritePropertyName("cacheFailureRecordTtl"); jsonWriter.WriteValue(_dnsServer.CacheZoneManager.FailureRecordTtl); jsonWriter.WritePropertyName("cachePrefetchEligibility"); jsonWriter.WriteValue(_dnsServer.CachePrefetchEligibility); jsonWriter.WritePropertyName("cachePrefetchTrigger"); jsonWriter.WriteValue(_dnsServer.CachePrefetchTrigger); jsonWriter.WritePropertyName("cachePrefetchSampleIntervalInMinutes"); jsonWriter.WriteValue(_dnsServer.CachePrefetchSampleIntervalInMinutes); jsonWriter.WritePropertyName("cachePrefetchSampleEligibilityHitsPerHour"); jsonWriter.WriteValue(_dnsServer.CachePrefetchSampleEligibilityHitsPerHour); jsonWriter.WritePropertyName("proxy"); if (_dnsServer.Proxy == null) { jsonWriter.WriteNull(); } else { jsonWriter.WriteStartObject(); NetProxy proxy = _dnsServer.Proxy; jsonWriter.WritePropertyName("type"); jsonWriter.WriteValue(proxy.Type.ToString()); jsonWriter.WritePropertyName("address"); jsonWriter.WriteValue(proxy.Address); jsonWriter.WritePropertyName("port"); jsonWriter.WriteValue(proxy.Port); NetworkCredential credential = proxy.Credential; if (credential != null) { jsonWriter.WritePropertyName("username"); jsonWriter.WriteValue(credential.UserName); jsonWriter.WritePropertyName("password"); jsonWriter.WriteValue(credential.Password); } jsonWriter.WritePropertyName("bypass"); jsonWriter.WriteStartArray(); foreach (NetProxyBypassItem item in proxy.BypassList) jsonWriter.WriteValue(item.Value); jsonWriter.WriteEndArray(); jsonWriter.WriteEndObject(); } jsonWriter.WritePropertyName("forwarders"); DnsTransportProtocol forwarderProtocol = DnsTransportProtocol.Udp; if (_dnsServer.Forwarders == null) { jsonWriter.WriteNull(); } else { forwarderProtocol = _dnsServer.Forwarders[0].Protocol; jsonWriter.WriteStartArray(); foreach (NameServerAddress forwarder in _dnsServer.Forwarders) jsonWriter.WriteValue(forwarder.OriginalAddress); jsonWriter.WriteEndArray(); } jsonWriter.WritePropertyName("forwarderProtocol"); jsonWriter.WriteValue(forwarderProtocol.ToString()); jsonWriter.WritePropertyName("enableBlocking"); jsonWriter.WriteValue(_dnsServer.EnableBlocking); if (!_dnsServer.EnableBlocking && (DateTime.UtcNow < _temporaryDisableBlockingTill)) { jsonWriter.WritePropertyName("temporaryDisableBlockingTill"); jsonWriter.WriteValue(_temporaryDisableBlockingTill); } jsonWriter.WritePropertyName("blockingType"); jsonWriter.WriteValue(_dnsServer.BlockingType.ToString()); jsonWriter.WritePropertyName("customBlockingAddresses"); jsonWriter.WriteStartArray(); foreach (DnsARecord record in _dnsServer.CustomBlockingARecords) jsonWriter.WriteValue(record.Address.ToString()); foreach (DnsAAAARecord record in _dnsServer.CustomBlockingAAAARecords) jsonWriter.WriteValue(record.Address.ToString()); jsonWriter.WriteEndArray(); jsonWriter.WritePropertyName("blockListUrls"); if ((_dnsServer.BlockListZoneManager.AllowListUrls.Count == 0) && (_dnsServer.BlockListZoneManager.BlockListUrls.Count == 0)) { jsonWriter.WriteNull(); } else { jsonWriter.WriteStartArray(); foreach (Uri allowListUrl in _dnsServer.BlockListZoneManager.AllowListUrls) jsonWriter.WriteValue("!" + allowListUrl.AbsoluteUri); foreach (Uri blockListUrl in _dnsServer.BlockListZoneManager.BlockListUrls) jsonWriter.WriteValue(blockListUrl.AbsoluteUri); jsonWriter.WriteEndArray(); } jsonWriter.WritePropertyName("blockListUpdateIntervalHours"); jsonWriter.WriteValue(_blockListUpdateIntervalHours); if (_blockListUpdateTimer is not null) { DateTime blockListNextUpdatedOn = _blockListLastUpdatedOn.AddHours(_blockListUpdateIntervalHours); jsonWriter.WritePropertyName("blockListNextUpdatedOn"); jsonWriter.WriteValue(blockListNextUpdatedOn); } } private void SetDnsSettings(HttpListenerRequest request, JsonTextWriter jsonWriter) { bool restartDnsService = false; bool restartWebService = false; string strDnsServerDomain = request.QueryString["dnsServerDomain"]; if (!string.IsNullOrEmpty(strDnsServerDomain)) _dnsServer.ServerDomain = strDnsServerDomain; string strDnsServerLocalEndPoints = request.QueryString["dnsServerLocalEndPoints"]; if (strDnsServerLocalEndPoints != null) { if (string.IsNullOrEmpty(strDnsServerLocalEndPoints)) strDnsServerLocalEndPoints = "0.0.0.0:53,[::]:53"; string[] strLocalEndPoints = strDnsServerLocalEndPoints.Split(new char[] { ',' }, StringSplitOptions.RemoveEmptyEntries); List localEndPoints = new List(strLocalEndPoints.Length); for (int i = 0; i < strLocalEndPoints.Length; i++) { NameServerAddress nameServer = new NameServerAddress(strLocalEndPoints[i]); if (nameServer.IPEndPoint != null) localEndPoints.Add(nameServer.IPEndPoint); } if (localEndPoints.Count > 0) { if (_dnsServer.LocalEndPoints.Count != localEndPoints.Count) { restartDnsService = true; } else { foreach (IPEndPoint currentLocalEP in _dnsServer.LocalEndPoints) { if (!localEndPoints.Contains(currentLocalEP)) { restartDnsService = true; break; } } } _dnsServer.LocalEndPoints = localEndPoints; } } string strWebServiceLocalAddresses = request.QueryString["webServiceLocalAddresses"]; if (strWebServiceLocalAddresses != null) { if (string.IsNullOrEmpty(strWebServiceLocalAddresses)) strWebServiceLocalAddresses = "0.0.0.0,[::]"; string[] strLocalAddresses = strWebServiceLocalAddresses.Split(new char[] { ',' }, StringSplitOptions.RemoveEmptyEntries); List localAddresses = new List(strLocalAddresses.Length); for (int i = 0; i < strLocalAddresses.Length; i++) { if (IPAddress.TryParse(strLocalAddresses[i], out IPAddress localAddress)) localAddresses.Add(localAddress); } if (localAddresses.Count > 0) { if (_webServiceLocalAddresses.Count != localAddresses.Count) { restartWebService = true; } else { foreach (IPAddress currentlocalAddress in _webServiceLocalAddresses) { if (!localAddresses.Contains(currentlocalAddress)) { restartWebService = true; break; } } } _webServiceLocalAddresses = localAddresses; } } int oldWebServiceHttpPort = _webServiceHttpPort; string strWebServiceHttpPort = request.QueryString["webServiceHttpPort"]; if (!string.IsNullOrEmpty(strWebServiceHttpPort)) { _webServiceHttpPort = int.Parse(strWebServiceHttpPort); if (oldWebServiceHttpPort != _webServiceHttpPort) restartWebService = true; } string strWebServiceEnableTls = request.QueryString["webServiceEnableTls"]; if (!string.IsNullOrEmpty(strWebServiceEnableTls)) { bool oldWebServiceEnableTls = _webServiceEnableTls; _webServiceEnableTls = bool.Parse(strWebServiceEnableTls); if (oldWebServiceEnableTls != _webServiceEnableTls) restartWebService = true; } string strWebServiceHttpToTlsRedirect = request.QueryString["webServiceHttpToTlsRedirect"]; if (!string.IsNullOrEmpty(strWebServiceHttpToTlsRedirect)) _webServiceHttpToTlsRedirect = bool.Parse(strWebServiceHttpToTlsRedirect); string strWebServiceTlsPort = request.QueryString["webServiceTlsPort"]; if (!string.IsNullOrEmpty(strWebServiceTlsPort)) { int oldWebServiceTlsPort = _webServiceTlsPort; _webServiceTlsPort = int.Parse(strWebServiceTlsPort); if (oldWebServiceTlsPort != _webServiceTlsPort) restartWebService = true; } string strWebServiceTlsCertificatePath = request.QueryString["webServiceTlsCertificatePath"]; string strWebServiceTlsCertificatePassword = request.QueryString["webServiceTlsCertificatePassword"]; if (string.IsNullOrEmpty(strWebServiceTlsCertificatePath)) { _webServiceTlsCertificatePath = null; _webServiceTlsCertificatePassword = ""; } else { if (strWebServiceTlsCertificatePassword == "************") strWebServiceTlsCertificatePassword = _webServiceTlsCertificatePassword; if ((strWebServiceTlsCertificatePath != _webServiceTlsCertificatePath) || (strWebServiceTlsCertificatePassword != _webServiceTlsCertificatePassword)) { LoadWebServiceTlsCertificate(strWebServiceTlsCertificatePath, strWebServiceTlsCertificatePassword); _webServiceTlsCertificatePath = strWebServiceTlsCertificatePath; _webServiceTlsCertificatePassword = strWebServiceTlsCertificatePassword; StartTlsCertificateUpdateTimer(); } } string enableDnsOverHttp = request.QueryString["enableDnsOverHttp"]; if (!string.IsNullOrEmpty(enableDnsOverHttp)) { bool oldEnableDnsOverHttp = _dnsServer.EnableDnsOverHttp; _dnsServer.EnableDnsOverHttp = bool.Parse(enableDnsOverHttp); if (oldEnableDnsOverHttp != _dnsServer.EnableDnsOverHttp) restartDnsService = true; } string strEnableDnsOverTls = request.QueryString["enableDnsOverTls"]; if (!string.IsNullOrEmpty(strEnableDnsOverTls)) { bool oldEnableDnsOverTls = _dnsServer.EnableDnsOverTls; _dnsServer.EnableDnsOverTls = bool.Parse(strEnableDnsOverTls); if (oldEnableDnsOverTls != _dnsServer.EnableDnsOverTls) restartDnsService = true; } string strEnableDnsOverHttps = request.QueryString["enableDnsOverHttps"]; if (!string.IsNullOrEmpty(strEnableDnsOverHttps)) { bool oldEnableDnsOverHttps = _dnsServer.EnableDnsOverHttps; _dnsServer.EnableDnsOverHttps = bool.Parse(strEnableDnsOverHttps); if (oldEnableDnsOverHttps != _dnsServer.EnableDnsOverHttps) restartDnsService = true; } string strDnsTlsCertificatePath = request.QueryString["dnsTlsCertificatePath"]; string strDnsTlsCertificatePassword = request.QueryString["dnsTlsCertificatePassword"]; if (string.IsNullOrEmpty(strDnsTlsCertificatePath)) { _dnsTlsCertificatePath = null; _dnsTlsCertificatePassword = ""; } else { if (strDnsTlsCertificatePassword == "************") strDnsTlsCertificatePassword = _dnsTlsCertificatePassword; if ((strDnsTlsCertificatePath != _dnsTlsCertificatePath) || (strDnsTlsCertificatePassword != _dnsTlsCertificatePassword)) { LoadDnsTlsCertificate(strDnsTlsCertificatePath, strDnsTlsCertificatePassword); _dnsTlsCertificatePath = strDnsTlsCertificatePath; _dnsTlsCertificatePassword = strDnsTlsCertificatePassword; StartTlsCertificateUpdateTimer(); } } string strTsigKeys = request.QueryString["tsigKeys"]; if (!string.IsNullOrEmpty(strTsigKeys)) { if (strTsigKeys == "false") { _dnsServer.TsigKeys = null; } else { string[] strTsigKeyParts = strTsigKeys.Split('|'); Dictionary tsigKeys = new Dictionary(strTsigKeyParts.Length); for (int i = 0; i < strTsigKeyParts.Length; i += 3) { string keyName = strTsigKeyParts[i + 0].ToLower(); string sharedSecret = strTsigKeyParts[i + 1]; string algorithmName = strTsigKeyParts[i + 2]; if (sharedSecret.Length == 0) { byte[] key = new byte[32]; _rng.GetBytes(key); tsigKeys.Add(keyName, new TsigKey(keyName, Convert.ToBase64String(key), algorithmName)); } else { tsigKeys.Add(keyName, new TsigKey(keyName, sharedSecret, algorithmName)); } } _dnsServer.TsigKeys = tsigKeys; } } string strPreferIPv6 = request.QueryString["preferIPv6"]; if (!string.IsNullOrEmpty(strPreferIPv6)) _dnsServer.PreferIPv6 = bool.Parse(strPreferIPv6); string strEnableLogging = request.QueryString["enableLogging"]; if (!string.IsNullOrEmpty(strEnableLogging)) _log.EnableLogging = bool.Parse(strEnableLogging); string strLogQueries = request.QueryString["logQueries"]; if (!string.IsNullOrEmpty(strLogQueries)) { if (bool.Parse(strLogQueries)) _dnsServer.QueryLogManager = _log; else _dnsServer.QueryLogManager = null; } string strUseLocalTime = request.QueryString["useLocalTime"]; if (!string.IsNullOrEmpty(strUseLocalTime)) _log.UseLocalTime = bool.Parse(strUseLocalTime); string strLogFolder = request.QueryString["logFolder"]; if (!string.IsNullOrEmpty(strLogFolder)) _log.LogFolder = strLogFolder; string strMaxLogFileDays = request.QueryString["maxLogFileDays"]; if (!string.IsNullOrEmpty(strMaxLogFileDays)) _log.MaxLogFileDays = int.Parse(strMaxLogFileDays); string strMaxStatFileDays = request.QueryString["maxStatFileDays"]; if (!string.IsNullOrEmpty(strMaxStatFileDays)) _dnsServer.StatsManager.MaxStatFileDays = int.Parse(strMaxStatFileDays); string strRecursion = request.QueryString["recursion"]; if (!string.IsNullOrEmpty(strRecursion)) _dnsServer.Recursion = Enum.Parse(strRecursion, true); string strRecursionDeniedNetworks = request.QueryString["recursionDeniedNetworks"]; if (!string.IsNullOrEmpty(strRecursionDeniedNetworks)) { if (strRecursionDeniedNetworks == "false") { _dnsServer.RecursionDeniedNetworks = null; } else { string[] strNetworks = strRecursionDeniedNetworks.Split(new char[] { ',' }, StringSplitOptions.RemoveEmptyEntries); NetworkAddress[] networks = new NetworkAddress[strNetworks.Length]; for (int i = 0; i < networks.Length; i++) networks[i] = NetworkAddress.Parse(strNetworks[i]); _dnsServer.RecursionDeniedNetworks = networks; } } string strRecursionAllowedNetworks = request.QueryString["recursionAllowedNetworks"]; if (!string.IsNullOrEmpty(strRecursionAllowedNetworks)) { if (strRecursionAllowedNetworks == "false") { _dnsServer.RecursionAllowedNetworks = null; } else { string[] strNetworks = strRecursionAllowedNetworks.Split(new char[] { ',' }, StringSplitOptions.RemoveEmptyEntries); NetworkAddress[] networks = new NetworkAddress[strNetworks.Length]; for (int i = 0; i < networks.Length; i++) networks[i] = NetworkAddress.Parse(strNetworks[i]); _dnsServer.RecursionAllowedNetworks = networks; } } string strRandomizeName = request.QueryString["randomizeName"]; if (!string.IsNullOrEmpty(strRandomizeName)) _dnsServer.RandomizeName = bool.Parse(strRandomizeName); string strQnameMinimization = request.QueryString["qnameMinimization"]; if (!string.IsNullOrEmpty(strQnameMinimization)) _dnsServer.QnameMinimization = bool.Parse(strQnameMinimization); string strNsRevalidation = request.QueryString["nsRevalidation"]; if (!string.IsNullOrEmpty(strNsRevalidation)) _dnsServer.NsRevalidation = bool.Parse(strNsRevalidation); string strQpmLimitRequests = request.QueryString["qpmLimitRequests"]; if (!string.IsNullOrEmpty(strQpmLimitRequests)) _dnsServer.QpmLimitRequests = int.Parse(strQpmLimitRequests); string strQpmLimitErrors = request.QueryString["qpmLimitErrors"]; if (!string.IsNullOrEmpty(strQpmLimitErrors)) _dnsServer.QpmLimitErrors = int.Parse(strQpmLimitErrors); string strQpmLimitSampleMinutes = request.QueryString["qpmLimitSampleMinutes"]; if (!string.IsNullOrEmpty(strQpmLimitSampleMinutes)) _dnsServer.QpmLimitSampleMinutes = int.Parse(strQpmLimitSampleMinutes); string strQpmLimitIPv4PrefixLength = request.QueryString["qpmLimitIPv4PrefixLength"]; if (!string.IsNullOrEmpty(strQpmLimitIPv4PrefixLength)) _dnsServer.QpmLimitIPv4PrefixLength = int.Parse(strQpmLimitIPv4PrefixLength); string strQpmLimitIPv6PrefixLength = request.QueryString["qpmLimitIPv6PrefixLength"]; if (!string.IsNullOrEmpty(strQpmLimitIPv6PrefixLength)) _dnsServer.QpmLimitIPv6PrefixLength = int.Parse(strQpmLimitIPv6PrefixLength); string strServeStale = request.QueryString["serveStale"]; if (!string.IsNullOrEmpty(strServeStale)) _dnsServer.ServeStale = bool.Parse(strServeStale); string strServeStaleTtl = request.QueryString["serveStaleTtl"]; if (!string.IsNullOrEmpty(strServeStaleTtl)) _dnsServer.CacheZoneManager.ServeStaleTtl = uint.Parse(strServeStaleTtl); string strCacheMinimumRecordTtl = request.QueryString["cacheMinimumRecordTtl"]; if (!string.IsNullOrEmpty(strCacheMinimumRecordTtl)) _dnsServer.CacheZoneManager.MinimumRecordTtl = uint.Parse(strCacheMinimumRecordTtl); string strCacheMaximumRecordTtl = request.QueryString["cacheMaximumRecordTtl"]; if (!string.IsNullOrEmpty(strCacheMaximumRecordTtl)) _dnsServer.CacheZoneManager.MaximumRecordTtl = uint.Parse(strCacheMaximumRecordTtl); string strCacheNegativeRecordTtl = request.QueryString["cacheNegativeRecordTtl"]; if (!string.IsNullOrEmpty(strCacheNegativeRecordTtl)) _dnsServer.CacheZoneManager.NegativeRecordTtl = uint.Parse(strCacheNegativeRecordTtl); string strCacheFailureRecordTtl = request.QueryString["cacheFailureRecordTtl"]; if (!string.IsNullOrEmpty(strCacheFailureRecordTtl)) _dnsServer.CacheZoneManager.FailureRecordTtl = uint.Parse(strCacheFailureRecordTtl); string strCachePrefetchEligibility = request.QueryString["cachePrefetchEligibility"]; if (!string.IsNullOrEmpty(strCachePrefetchEligibility)) _dnsServer.CachePrefetchEligibility = int.Parse(strCachePrefetchEligibility); string strCachePrefetchTrigger = request.QueryString["cachePrefetchTrigger"]; if (!string.IsNullOrEmpty(strCachePrefetchTrigger)) _dnsServer.CachePrefetchTrigger = int.Parse(strCachePrefetchTrigger); string strCachePrefetchSampleIntervalInMinutes = request.QueryString["cachePrefetchSampleIntervalInMinutes"]; if (!string.IsNullOrEmpty(strCachePrefetchSampleIntervalInMinutes)) _dnsServer.CachePrefetchSampleIntervalInMinutes = int.Parse(strCachePrefetchSampleIntervalInMinutes); string strCachePrefetchSampleEligibilityHitsPerHour = request.QueryString["cachePrefetchSampleEligibilityHitsPerHour"]; if (!string.IsNullOrEmpty(strCachePrefetchSampleEligibilityHitsPerHour)) _dnsServer.CachePrefetchSampleEligibilityHitsPerHour = int.Parse(strCachePrefetchSampleEligibilityHitsPerHour); string strProxyType = request.QueryString["proxyType"]; if (!string.IsNullOrEmpty(strProxyType)) { NetProxyType proxyType = (NetProxyType)Enum.Parse(typeof(NetProxyType), strProxyType, true); if (proxyType == NetProxyType.None) { _dnsServer.Proxy = null; } else { NetworkCredential credential = null; string strUsername = request.QueryString["proxyUsername"]; if (!string.IsNullOrEmpty(strUsername)) credential = new NetworkCredential(strUsername, request.QueryString["proxyPassword"]); _dnsServer.Proxy = NetProxy.CreateProxy(proxyType, request.QueryString["proxyAddress"], int.Parse(request.QueryString["proxyPort"]), credential); string strProxyBypass = request.QueryString["proxyBypass"]; if (!string.IsNullOrEmpty(strProxyBypass)) { string[] strBypassList = strProxyBypass.Split(new char[] { ',' }, StringSplitOptions.RemoveEmptyEntries); List bypassList = new List(strBypassList.Length); for (int i = 0; i < strBypassList.Length; i++) bypassList.Add(new NetProxyBypassItem(strBypassList[i])); _dnsServer.Proxy.BypassList = bypassList; } } } DnsTransportProtocol forwarderProtocol = DnsTransportProtocol.Udp; string strForwarderProtocol = request.QueryString["forwarderProtocol"]; if (!string.IsNullOrEmpty(strForwarderProtocol)) forwarderProtocol = (DnsTransportProtocol)Enum.Parse(typeof(DnsTransportProtocol), strForwarderProtocol, true); string strForwarders = request.QueryString["forwarders"]; if (!string.IsNullOrEmpty(strForwarders)) { if (strForwarders == "false") { _dnsServer.Forwarders = null; } else { string[] strForwardersList = strForwarders.Split(new char[] { ',' }, StringSplitOptions.RemoveEmptyEntries); NameServerAddress[] forwarders = new NameServerAddress[strForwardersList.Length]; for (int i = 0; i < strForwardersList.Length; i++) { if ((forwarderProtocol == DnsTransportProtocol.Tls) && IPAddress.TryParse(strForwardersList[i], out _)) strForwardersList[i] += ":853"; forwarders[i] = new NameServerAddress(strForwardersList[i], forwarderProtocol); } _dnsServer.Forwarders = forwarders; } } string strEnableBlocking = request.QueryString["enableBlocking"]; if (!string.IsNullOrEmpty(strEnableBlocking)) { _dnsServer.EnableBlocking = bool.Parse(strEnableBlocking); if (_dnsServer.EnableBlocking) { if (_temporaryDisableBlockingTimer is not null) _temporaryDisableBlockingTimer.Dispose(); } } string strBlockingType = request.QueryString["blockingType"]; if (!string.IsNullOrEmpty(strBlockingType)) _dnsServer.BlockingType = Enum.Parse(strBlockingType, true); string strCustomBlockingAddresses = request.QueryString["customBlockingAddresses"]; if (!string.IsNullOrEmpty(strCustomBlockingAddresses)) { if (strCustomBlockingAddresses == "false") { _dnsServer.CustomBlockingARecords = null; _dnsServer.CustomBlockingAAAARecords = null; } else { string[] strAddresses = strCustomBlockingAddresses.Split(new char[] { ',' }, StringSplitOptions.RemoveEmptyEntries); List dnsARecords = new List(); List dnsAAAARecords = new List(); foreach (string strAddress in strAddresses) { if (IPAddress.TryParse(strAddress, out IPAddress customAddress)) { switch (customAddress.AddressFamily) { case AddressFamily.InterNetwork: dnsARecords.Add(new DnsARecord(customAddress)); break; case AddressFamily.InterNetworkV6: dnsAAAARecords.Add(new DnsAAAARecord(customAddress)); break; } } } _dnsServer.CustomBlockingARecords = dnsARecords; _dnsServer.CustomBlockingAAAARecords = dnsAAAARecords; } } string strBlockListUrls = request.QueryString["blockListUrls"]; if (!string.IsNullOrEmpty(strBlockListUrls)) { if (strBlockListUrls == "false") { StopBlockListUpdateTimer(); _dnsServer.BlockListZoneManager.AllowListUrls.Clear(); _dnsServer.BlockListZoneManager.BlockListUrls.Clear(); _dnsServer.BlockListZoneManager.Flush(); } else { bool updated = false; string[] strBlockListUrlList = strBlockListUrls.Split(new char[] { ',' }, StringSplitOptions.RemoveEmptyEntries); if (oldWebServiceHttpPort != _webServiceHttpPort) { for (int i = 0; i < strBlockListUrlList.Length; i++) { if (strBlockListUrlList[i].Contains("http://localhost:" + oldWebServiceHttpPort + "/blocklist.txt")) { strBlockListUrlList[i] = "http://localhost:" + _webServiceHttpPort + "/blocklist.txt"; updated = true; break; } } } if (!updated) { if (strBlockListUrlList.Length != (_dnsServer.BlockListZoneManager.AllowListUrls.Count + _dnsServer.BlockListZoneManager.BlockListUrls.Count)) { updated = true; } else { foreach (string strBlockListUrl in strBlockListUrlList) { if (strBlockListUrl.StartsWith("!")) { string strAllowListUrl = strBlockListUrl.Substring(1); if (!_dnsServer.BlockListZoneManager.AllowListUrls.Contains(new Uri(strAllowListUrl))) { updated = true; break; } } else { if (!_dnsServer.BlockListZoneManager.BlockListUrls.Contains(new Uri(strBlockListUrl))) { updated = true; break; } } } } } if (updated) { _dnsServer.BlockListZoneManager.AllowListUrls.Clear(); _dnsServer.BlockListZoneManager.BlockListUrls.Clear(); foreach (string strBlockListUrl in strBlockListUrlList) { if (strBlockListUrl.StartsWith("!")) { Uri allowListUrl = new Uri(strBlockListUrl.Substring(1)); if (!_dnsServer.BlockListZoneManager.AllowListUrls.Contains(allowListUrl)) _dnsServer.BlockListZoneManager.AllowListUrls.Add(allowListUrl); } else { Uri blockListUrl = new Uri(strBlockListUrl); if (!_dnsServer.BlockListZoneManager.BlockListUrls.Contains(blockListUrl)) _dnsServer.BlockListZoneManager.BlockListUrls.Add(blockListUrl); } } ForceUpdateBlockLists(); } } } string strBlockListUpdateIntervalHours = request.QueryString["blockListUpdateIntervalHours"]; if (!string.IsNullOrEmpty(strBlockListUpdateIntervalHours)) { int blockListUpdateIntervalHours = int.Parse(strBlockListUpdateIntervalHours); if ((blockListUpdateIntervalHours < 1) || (blockListUpdateIntervalHours > 168)) throw new DnsWebServiceException("Parameter `blockListUpdateIntervalHours` must be between 1 hour and 168 hours (7 days)."); _blockListUpdateIntervalHours = blockListUpdateIntervalHours; } SaveConfigFile(); _log.Save(); _log.Write(GetRequestRemoteEndPoint(request), "[" + GetSession(request).Username + "] DNS Settings were updated {dnsServerDomain: " + _dnsServer.ServerDomain + "; dnsServerLocalEndPoints: " + strDnsServerLocalEndPoints + "; webServiceLocalAddresses: " + strWebServiceLocalAddresses + "; webServiceHttpPort: " + _webServiceHttpPort + "; webServiceEnableTls: " + strWebServiceEnableTls + "; webServiceHttpToTlsRedirect: " + strWebServiceHttpToTlsRedirect + "; webServiceTlsPort: " + strWebServiceTlsPort + "; webServiceTlsCertificatePath: " + strWebServiceTlsCertificatePath + "; enableDnsOverHttp: " + _dnsServer.EnableDnsOverHttp + "; enableDnsOverTls: " + _dnsServer.EnableDnsOverTls + "; enableDnsOverHttps: " + _dnsServer.EnableDnsOverHttps + "; dnsTlsCertificatePath: " + _dnsTlsCertificatePath + "; preferIPv6: " + _dnsServer.PreferIPv6 + "; enableLogging: " + strEnableLogging + "; logQueries: " + (_dnsServer.QueryLogManager != null) + "; useLocalTime: " + strUseLocalTime + "; logFolder: " + strLogFolder + "; maxLogFileDays: " + strMaxLogFileDays + "; recursion: " + _dnsServer.Recursion.ToString() + "; randomizeName: " + strRandomizeName + "; qnameMinimization: " + strQnameMinimization + "; serveStale: " + strServeStale + "; serveStaleTtl: " + strServeStaleTtl + "; cachePrefetchEligibility: " + strCachePrefetchEligibility + "; cachePrefetchTrigger: " + strCachePrefetchTrigger + "; cachePrefetchSampleIntervalInMinutes: " + strCachePrefetchSampleIntervalInMinutes + "; cachePrefetchSampleEligibilityHitsPerHour: " + strCachePrefetchSampleEligibilityHitsPerHour + "; proxyType: " + strProxyType + "; forwarders: " + strForwarders + "; forwarderProtocol: " + strForwarderProtocol + "; enableBlocking: " + _dnsServer.EnableBlocking + "; blockingType: " + _dnsServer.BlockingType.ToString() + "; blockListUrl: " + strBlockListUrls + "; blockListUpdateIntervalHours: " + strBlockListUpdateIntervalHours + ";}"); if ((_webServiceTlsCertificatePath == null) && (_dnsTlsCertificatePath == null)) StopTlsCertificateUpdateTimer(); GetDnsSettings(jsonWriter); RestartService(restartDnsService, restartWebService); } private void RestartService(bool restartDnsService, bool restartWebService) { if (restartDnsService) { _ = Task.Run(delegate () { _log.Write("Attempting to restart DNS service."); try { _dnsServer.Stop(); _dnsServer.Start(); _log.Write("DNS service was restarted successfully."); } catch (Exception ex) { _log.Write("Failed to restart DNS service."); _log.Write(ex); } }); } if (restartWebService) { _ = Task.Run(async delegate () { await Task.Delay(2000); //wait for this HTTP response to be delivered before stopping web server _log.Write("Attempting to restart web service."); try { StopDnsWebService(); StartDnsWebService(); _log.Write("Web service was restarted successfully."); } catch (Exception ex) { _log.Write("Failed to restart web service."); _log.Write(ex); } }); } } private async Task BackupSettingsAsync(HttpListenerRequest request, HttpListenerResponse response) { bool blockLists = false; bool logs = false; bool scopes = false; bool apps = false; bool stats = false; bool zones = false; bool allowedZones = false; bool blockedZones = false; bool dnsSettings = false; bool logSettings = false; string strBlockLists = request.QueryString["blockLists"]; if (!string.IsNullOrEmpty(strBlockLists)) blockLists = bool.Parse(strBlockLists); string strLogs = request.QueryString["logs"]; if (!string.IsNullOrEmpty(strLogs)) logs = bool.Parse(strLogs); string strScopes = request.QueryString["scopes"]; if (!string.IsNullOrEmpty(strScopes)) scopes = bool.Parse(strScopes); string strApps = request.QueryString["apps"]; if (!string.IsNullOrEmpty(strApps)) apps = bool.Parse(strApps); string strStats = request.QueryString["stats"]; if (!string.IsNullOrEmpty(strStats)) stats = bool.Parse(strStats); string strZones = request.QueryString["zones"]; if (!string.IsNullOrEmpty(strZones)) zones = bool.Parse(strZones); string strAllowedZones = request.QueryString["allowedZones"]; if (!string.IsNullOrEmpty(strAllowedZones)) allowedZones = bool.Parse(strAllowedZones); string strBlockedZones = request.QueryString["blockedZones"]; if (!string.IsNullOrEmpty(strBlockedZones)) blockedZones = bool.Parse(strBlockedZones); string strDnsSettings = request.QueryString["dnsSettings"]; if (!string.IsNullOrEmpty(strDnsSettings)) dnsSettings = bool.Parse(strDnsSettings); string strLogSettings = request.QueryString["logSettings"]; if (!string.IsNullOrEmpty(strLogSettings)) logSettings = bool.Parse(strLogSettings); string tmpFile = Path.GetTempFileName(); try { using (FileStream backupZipStream = new FileStream(tmpFile, FileMode.Create, FileAccess.ReadWrite)) { //create backup zip using (ZipArchive backupZip = new ZipArchive(backupZipStream, ZipArchiveMode.Create, true, Encoding.UTF8)) { if (blockLists) { string[] blockListFiles = Directory.GetFiles(Path.Combine(_configFolder, "blocklists"), "*", SearchOption.TopDirectoryOnly); foreach (string blockListFile in blockListFiles) { string entryName = "blocklists/" + Path.GetFileName(blockListFile); backupZip.CreateEntryFromFile(blockListFile, entryName); } } if (logs) { string[] logFiles = Directory.GetFiles(_log.LogFolderAbsolutePath, "*.log", SearchOption.TopDirectoryOnly); foreach (string logFile in logFiles) { string entryName = "logs/" + Path.GetFileName(logFile); if (logFile.Equals(_log.CurrentLogFile, StringComparison.OrdinalIgnoreCase)) { using (FileStream fS = new FileStream(logFile, FileMode.Open, FileAccess.Read, FileShare.ReadWrite)) { ZipArchiveEntry entry = backupZip.CreateEntry(entryName); using (Stream s = entry.Open()) { await fS.CopyToAsync(s); } } } else { backupZip.CreateEntryFromFile(logFile, entryName); } } } if (scopes) { string[] scopeFiles = Directory.GetFiles(Path.Combine(_configFolder, "scopes"), "*.scope", SearchOption.TopDirectoryOnly); foreach (string scopeFile in scopeFiles) { string entryName = "scopes/" + Path.GetFileName(scopeFile); backupZip.CreateEntryFromFile(scopeFile, entryName); } } if (apps) { string[] appFiles = Directory.GetFiles(Path.Combine(_configFolder, "apps"), "*", SearchOption.AllDirectories); foreach (string appFile in appFiles) { string entryName = appFile.Substring(_configFolder.Length); if (Path.DirectorySeparatorChar != '/') entryName = entryName.Replace(Path.DirectorySeparatorChar, '/'); entryName = entryName.TrimStart('/'); backupZip.CreateEntryFromFile(appFile, entryName); } } if (stats) { string[] hourlyStatsFiles = Directory.GetFiles(Path.Combine(_configFolder, "stats"), "*.stat", SearchOption.TopDirectoryOnly); foreach (string hourlyStatsFile in hourlyStatsFiles) { string entryName = "stats/" + Path.GetFileName(hourlyStatsFile); backupZip.CreateEntryFromFile(hourlyStatsFile, entryName); } string[] dailyStatsFiles = Directory.GetFiles(Path.Combine(_configFolder, "stats"), "*.dstat", SearchOption.TopDirectoryOnly); foreach (string dailyStatsFile in dailyStatsFiles) { string entryName = "stats/" + Path.GetFileName(dailyStatsFile); backupZip.CreateEntryFromFile(dailyStatsFile, entryName); } } if (zones) { string[] zoneFiles = Directory.GetFiles(Path.Combine(_configFolder, "zones"), "*.zone", SearchOption.TopDirectoryOnly); foreach (string zoneFile in zoneFiles) { string entryName = "zones/" + Path.GetFileName(zoneFile); backupZip.CreateEntryFromFile(zoneFile, entryName); } } if (allowedZones) { string allowedZonesFile = Path.Combine(_configFolder, "allowed.config"); if (File.Exists(allowedZonesFile)) backupZip.CreateEntryFromFile(allowedZonesFile, "allowed.config"); } if (blockedZones) { string blockedZonesFile = Path.Combine(_configFolder, "blocked.config"); if (File.Exists(blockedZonesFile)) backupZip.CreateEntryFromFile(blockedZonesFile, "blocked.config"); } if (dnsSettings) { string dnsSettingsFile = Path.Combine(_configFolder, "dns.config"); if (File.Exists(dnsSettingsFile)) backupZip.CreateEntryFromFile(dnsSettingsFile, "dns.config"); } if (logSettings) { string logSettingsFile = Path.Combine(_configFolder, "log.config"); if (File.Exists(logSettingsFile)) backupZip.CreateEntryFromFile(logSettingsFile, "log.config"); } } //send zip file backupZipStream.Position = 0; response.ContentType = "application/zip"; response.ContentLength64 = backupZipStream.Length; response.AddHeader("Content-Disposition", "attachment;filename=DnsServerBackup.zip"); using (Stream output = response.OutputStream) { await backupZipStream.CopyToAsync(output); } } } finally { try { File.Delete(tmpFile); } catch (Exception ex) { _log.Write(ex); } } _log.Write(GetRequestRemoteEndPoint(request), "[" + GetSession(request).Username + "] Settings backup zip file was exported."); } private async Task RestoreSettingsAsync(HttpListenerRequest request, JsonTextWriter jsonWriter) { bool blockLists = false; bool logs = false; bool scopes = false; bool apps = false; bool stats = false; bool zones = false; bool allowedZones = false; bool blockedZones = false; bool dnsSettings = false; bool logSettings = false; bool deleteExistingFiles = false; string strBlockLists = request.QueryString["blockLists"]; if (!string.IsNullOrEmpty(strBlockLists)) blockLists = bool.Parse(strBlockLists); string strLogs = request.QueryString["logs"]; if (!string.IsNullOrEmpty(strLogs)) logs = bool.Parse(strLogs); string strScopes = request.QueryString["scopes"]; if (!string.IsNullOrEmpty(strScopes)) scopes = bool.Parse(strScopes); string strApps = request.QueryString["apps"]; if (!string.IsNullOrEmpty(strApps)) apps = bool.Parse(strApps); string strStats = request.QueryString["stats"]; if (!string.IsNullOrEmpty(strStats)) stats = bool.Parse(strStats); string strZones = request.QueryString["zones"]; if (!string.IsNullOrEmpty(strZones)) zones = bool.Parse(strZones); string strAllowedZones = request.QueryString["allowedZones"]; if (!string.IsNullOrEmpty(strAllowedZones)) allowedZones = bool.Parse(strAllowedZones); string strBlockedZones = request.QueryString["blockedZones"]; if (!string.IsNullOrEmpty(strBlockedZones)) blockedZones = bool.Parse(strBlockedZones); string strDnsSettings = request.QueryString["dnsSettings"]; if (!string.IsNullOrEmpty(strDnsSettings)) dnsSettings = bool.Parse(strDnsSettings); string strLogSettings = request.QueryString["logSettings"]; if (!string.IsNullOrEmpty(strLogSettings)) logSettings = bool.Parse(strLogSettings); string strDeleteExistingFiles = request.QueryString["deleteExistingFiles"]; if (!string.IsNullOrEmpty(strDeleteExistingFiles)) deleteExistingFiles = bool.Parse(strDeleteExistingFiles); #region skip to content int crlfCount = 0; int byteRead; while (crlfCount != 4) { byteRead = request.InputStream.ReadByte(); switch (byteRead) { case -1: throw new EndOfStreamException(); case 13: //CR case 10: //LF crlfCount++; break; default: crlfCount = 0; break; } } #endregion //write to temp file string tmpFile = Path.GetTempFileName(); try { using (FileStream fS = new FileStream(tmpFile, FileMode.Create, FileAccess.ReadWrite)) { await request.InputStream.CopyToAsync(fS); fS.Position = 0; using (ZipArchive backupZip = new ZipArchive(fS, ZipArchiveMode.Read, false, Encoding.UTF8)) { if (logSettings || logs) { //stop logging _log.StopLogging(); } try { if (logSettings) { ZipArchiveEntry entry = backupZip.GetEntry("log.config"); if (entry != null) entry.ExtractToFile(Path.Combine(_configFolder, entry.Name), true); //reload config _log.LoadConfig(); } if (logs) { if (deleteExistingFiles) { //delete existing log files string[] logFiles = Directory.GetFiles(_log.LogFolderAbsolutePath, "*.log", SearchOption.TopDirectoryOnly); foreach (string logFile in logFiles) { File.Delete(logFile); } } //extract log files from backup foreach (ZipArchiveEntry entry in backupZip.Entries) { if (entry.FullName.StartsWith("logs/")) entry.ExtractToFile(Path.Combine(_log.LogFolderAbsolutePath, entry.Name), true); } } } finally { if (logSettings || logs) { //start logging if (_log.EnableLogging) _log.StartLogging(); } } if (blockLists) { if (deleteExistingFiles) { //delete existing block list files string[] blockListFiles = Directory.GetFiles(Path.Combine(_configFolder, "blocklists"), "*", SearchOption.TopDirectoryOnly); foreach (string blockListFile in blockListFiles) { File.Delete(blockListFile); } } //extract block list files from backup foreach (ZipArchiveEntry entry in backupZip.Entries) { if (entry.FullName.StartsWith("blocklists/")) entry.ExtractToFile(Path.Combine(_configFolder, "blocklists", entry.Name), true); } } if (scopes) { //stop dhcp server _dhcpServer.Stop(); try { if (deleteExistingFiles) { //delete existing scope files string[] scopeFiles = Directory.GetFiles(Path.Combine(_configFolder, "scopes"), "*.scope", SearchOption.TopDirectoryOnly); foreach (string scopeFile in scopeFiles) { File.Delete(scopeFile); } } //extract scope files from backup foreach (ZipArchiveEntry entry in backupZip.Entries) { if (entry.FullName.StartsWith("scopes/")) entry.ExtractToFile(Path.Combine(_configFolder, "scopes", entry.Name), true); } } finally { //start dhcp server _dhcpServer.Start(); } } if (apps) { //unload apps _dnsServer.DnsApplicationManager.UnloadAllApplications(); if (deleteExistingFiles) { //delete existing apps string appFolder = Path.Combine(_configFolder, "apps"); if (Directory.Exists(appFolder)) Directory.Delete(appFolder, true); //create apps folder Directory.CreateDirectory(appFolder); } //extract apps files from backup foreach (ZipArchiveEntry entry in backupZip.Entries) { if (entry.FullName.StartsWith("apps/")) { string entryPath = entry.FullName; if (Path.DirectorySeparatorChar != '/') entryPath = entryPath.Replace('/', '\\'); string filePath = Path.Combine(_configFolder, entryPath); Directory.CreateDirectory(Path.GetDirectoryName(filePath)); entry.ExtractToFile(filePath, true); } } //reload apps _dnsServer.DnsApplicationManager.LoadAllApplications(); } if (stats) { if (deleteExistingFiles) { //delete existing stats files string[] hourlyStatsFiles = Directory.GetFiles(Path.Combine(_configFolder, "stats"), "*.stat", SearchOption.TopDirectoryOnly); foreach (string hourlyStatsFile in hourlyStatsFiles) { File.Delete(hourlyStatsFile); } string[] dailyStatsFiles = Directory.GetFiles(Path.Combine(_configFolder, "stats"), "*.dstat", SearchOption.TopDirectoryOnly); foreach (string dailyStatsFile in dailyStatsFiles) { File.Delete(dailyStatsFile); } } //extract stats files from backup foreach (ZipArchiveEntry entry in backupZip.Entries) { if (entry.FullName.StartsWith("stats/")) entry.ExtractToFile(Path.Combine(_configFolder, "stats", entry.Name), true); } //reload stats _dnsServer.StatsManager.ReloadStats(); } if (zones) { if (deleteExistingFiles) { //delete existing zone files string[] zoneFiles = Directory.GetFiles(Path.Combine(_configFolder, "zones"), "*.zone", SearchOption.TopDirectoryOnly); foreach (string zoneFile in zoneFiles) { File.Delete(zoneFile); } } //extract zone files from backup foreach (ZipArchiveEntry entry in backupZip.Entries) { if (entry.FullName.StartsWith("zones/")) entry.ExtractToFile(Path.Combine(_configFolder, "zones", entry.Name), true); } //reload zones _dnsServer.AuthZoneManager.LoadAllZoneFiles(); } if (allowedZones) { ZipArchiveEntry entry = backupZip.GetEntry("allowed.config"); if (entry == null) { string fileName = Path.Combine(_configFolder, "allowed.config"); if (File.Exists(fileName)) File.Delete(fileName); } else { entry.ExtractToFile(Path.Combine(_configFolder, entry.Name), true); } //reload _dnsServer.AllowedZoneManager.LoadAllowedZoneFile(); } if (blockedZones) { ZipArchiveEntry entry = backupZip.GetEntry("blocked.config"); if (entry == null) { string fileName = Path.Combine(_configFolder, "allowed.config"); if (File.Exists(fileName)) File.Delete(fileName); } else { entry.ExtractToFile(Path.Combine(_configFolder, entry.Name), true); } //reload _dnsServer.BlockedZoneManager.LoadBlockedZoneFile(); } if (dnsSettings) { ZipArchiveEntry entry = backupZip.GetEntry("dns.config"); if (entry != null) entry.ExtractToFile(Path.Combine(_configFolder, entry.Name), true); //reload settings and block list zone LoadConfigFile(); _dnsServer.BlockListZoneManager.LoadBlockLists(); } _log.Write(GetRequestRemoteEndPoint(request), "[" + GetSession(request).Username + "] Settings backup zip file was restored."); } } } finally { try { File.Delete(tmpFile); } catch (Exception ex) { _log.Write(ex); } } if (dnsSettings) RestartService(true, true); GetDnsSettings(jsonWriter); } private void ForceUpdateBlockLists(HttpListenerRequest request) { if (ForceUpdateBlockLists()) _log.Write(GetRequestRemoteEndPoint(request), "[" + GetSession(request).Username + "] Block list update was triggered."); } private void TemporaryDisableBlocking(HttpListenerRequest request, JsonTextWriter jsonWriter) { string strMinutes = request.QueryString["minutes"]; if (string.IsNullOrEmpty(strMinutes)) throw new DnsWebServiceException("Parameter 'minutes' missing."); int minutes = int.Parse(strMinutes); Timer temporaryDisableBlockingTimer = _temporaryDisableBlockingTimer; if (temporaryDisableBlockingTimer is not null) temporaryDisableBlockingTimer.Dispose(); Timer newTemporaryDisableBlockingTimer = new Timer(delegate (object state) { try { _dnsServer.EnableBlocking = true; _log.Write(GetRequestRemoteEndPoint(request), "[" + GetSession(request).Username + "] Blocking was enabled after " + minutes + " minute(s) being temporarily disabled."); } catch (Exception ex) { _log.Write(ex); } }); Timer originalTimer = Interlocked.CompareExchange(ref _temporaryDisableBlockingTimer, newTemporaryDisableBlockingTimer, temporaryDisableBlockingTimer); if (ReferenceEquals(originalTimer, temporaryDisableBlockingTimer)) { newTemporaryDisableBlockingTimer.Change(minutes * 60 * 1000, Timeout.Infinite); _dnsServer.EnableBlocking = false; _temporaryDisableBlockingTill = DateTime.UtcNow.AddMinutes(minutes); _log.Write(GetRequestRemoteEndPoint(request), "[" + GetSession(request).Username + "] Blocking was temporarily disabled for " + minutes + " minute(s)."); } else { newTemporaryDisableBlockingTimer.Dispose(); } jsonWriter.WritePropertyName("temporaryDisableBlockingTill"); jsonWriter.WriteValue(_temporaryDisableBlockingTill); } #endregion #region dashboard private async Task GetStats(HttpListenerRequest request, JsonTextWriter jsonWriter) { string strType = request.QueryString["type"]; if (string.IsNullOrEmpty(strType)) strType = "lastHour"; Dictionary>> data; switch (strType) { case "lastHour": data = _dnsServer.StatsManager.GetLastHourMinuteWiseStats(); break; case "lastDay": data = _dnsServer.StatsManager.GetLastDayHourWiseStats(); break; case "lastWeek": data = _dnsServer.StatsManager.GetLastWeekDayWiseStats(); break; case "lastMonth": data = _dnsServer.StatsManager.GetLastMonthDayWiseStats(); break; case "lastYear": data = _dnsServer.StatsManager.GetLastYearMonthWiseStats(); break; case "custom": string strStartDate = request.QueryString["start"]; if (string.IsNullOrEmpty(strStartDate)) throw new DnsWebServiceException("Parameter 'start' missing."); string strEndDate = request.QueryString["end"]; if (string.IsNullOrEmpty(strEndDate)) throw new DnsWebServiceException("Parameter 'end' missing."); if (!DateTime.TryParseExact(strStartDate, "yyyy-M-d", CultureInfo.InvariantCulture, DateTimeStyles.AssumeLocal | DateTimeStyles.AdjustToUniversal, out DateTime startDate)) throw new DnsWebServiceException("Invalid start date format."); if (!DateTime.TryParseExact(strEndDate, "yyyy-M-d", CultureInfo.InvariantCulture, DateTimeStyles.AssumeLocal | DateTimeStyles.AdjustToUniversal, out DateTime endDate)) throw new DnsWebServiceException("Invalid end date format."); if (startDate > endDate) throw new DnsWebServiceException("Start date must be less than or equal to end date."); if ((Convert.ToInt32((endDate - startDate).TotalDays) + 1) > 7) data = _dnsServer.StatsManager.GetDayWiseStats(startDate, endDate); else data = _dnsServer.StatsManager.GetHourWiseStats(startDate, endDate); break; default: throw new DnsWebServiceException("Unknown stats type requested: " + strType); } //stats { List> stats = data["stats"]; jsonWriter.WritePropertyName("stats"); jsonWriter.WriteStartObject(); foreach (KeyValuePair item in stats) { jsonWriter.WritePropertyName(item.Key); jsonWriter.WriteValue(item.Value); } jsonWriter.WritePropertyName("zones"); jsonWriter.WriteValue(_dnsServer.AuthZoneManager.TotalZones); jsonWriter.WritePropertyName("allowedZones"); jsonWriter.WriteValue(_dnsServer.AllowedZoneManager.TotalZonesAllowed); jsonWriter.WritePropertyName("blockedZones"); jsonWriter.WriteValue(_dnsServer.BlockedZoneManager.TotalZonesBlocked); jsonWriter.WritePropertyName("blockListZones"); jsonWriter.WriteValue(_dnsServer.BlockListZoneManager.TotalZonesBlocked); jsonWriter.WriteEndObject(); } //main chart { jsonWriter.WritePropertyName("mainChartData"); jsonWriter.WriteStartObject(); //label { List> statsPerInterval = data["totalQueriesPerInterval"]; jsonWriter.WritePropertyName("labels"); jsonWriter.WriteStartArray(); foreach (KeyValuePair item in statsPerInterval) jsonWriter.WriteValue(item.Key); jsonWriter.WriteEndArray(); } //datasets { jsonWriter.WritePropertyName("datasets"); jsonWriter.WriteStartArray(); WriteChartDataSet(jsonWriter, "Total", "rgba(102, 153, 255, 0.1)", "rgb(102, 153, 255)", data["totalQueriesPerInterval"]); WriteChartDataSet(jsonWriter, "No Error", "rgba(92, 184, 92, 0.1)", "rgb(92, 184, 92)", data["totalNoErrorPerInterval"]); WriteChartDataSet(jsonWriter, "Server Failure", "rgba(217, 83, 79, 0.1)", "rgb(217, 83, 79)", data["totalServerFailurePerInterval"]); WriteChartDataSet(jsonWriter, "NX Domain", "rgba(7, 7, 7, 0.1)", "rgb(7, 7, 7)", data["totalNxDomainPerInterval"]); WriteChartDataSet(jsonWriter, "Refused", "rgba(91, 192, 222, 0.1)", "rgb(91, 192, 222)", data["totalRefusedPerInterval"]); WriteChartDataSet(jsonWriter, "Authoritative", "rgba(150, 150, 0, 0.1)", "rgb(150, 150, 0)", data["totalAuthHitPerInterval"]); WriteChartDataSet(jsonWriter, "Recursive", "rgba(23, 162, 184, 0.1)", "rgb(23, 162, 184)", data["totalRecursionsPerInterval"]); WriteChartDataSet(jsonWriter, "Cached", "rgba(111, 84, 153, 0.1)", "rgb(111, 84, 153)", data["totalCacheHitPerInterval"]); WriteChartDataSet(jsonWriter, "Blocked", "rgba(255, 165, 0, 0.1)", "rgb(255, 165, 0)", data["totalBlockedPerInterval"]); WriteChartDataSet(jsonWriter, "Clients", "rgba(51, 122, 183, 0.1)", "rgb(51, 122, 183)", data["totalClientsPerInterval"]); jsonWriter.WriteEndArray(); } jsonWriter.WriteEndObject(); } //query response chart { jsonWriter.WritePropertyName("queryResponseChartData"); jsonWriter.WriteStartObject(); List> stats = data["stats"]; //labels { jsonWriter.WritePropertyName("labels"); jsonWriter.WriteStartArray(); foreach (KeyValuePair item in stats) { switch (item.Key) { case "totalAuthoritative": jsonWriter.WriteValue("Authoritative"); break; case "totalRecursive": jsonWriter.WriteValue("Recursive"); break; case "totalCached": jsonWriter.WriteValue("Cached"); break; case "totalBlocked": jsonWriter.WriteValue("Blocked"); break; } } jsonWriter.WriteEndArray(); } //datasets { jsonWriter.WritePropertyName("datasets"); jsonWriter.WriteStartArray(); jsonWriter.WriteStartObject(); jsonWriter.WritePropertyName("data"); jsonWriter.WriteStartArray(); foreach (KeyValuePair item in stats) { switch (item.Key) { case "totalAuthoritative": case "totalRecursive": case "totalCached": case "totalBlocked": jsonWriter.WriteValue(item.Value); break; } } jsonWriter.WriteEndArray(); jsonWriter.WritePropertyName("backgroundColor"); jsonWriter.WriteStartArray(); jsonWriter.WriteValue("rgba(150, 150, 0, 0.5)"); jsonWriter.WriteValue("rgba(23, 162, 184, 0.5)"); jsonWriter.WriteValue("rgba(111, 84, 153, 0.5)"); jsonWriter.WriteValue("rgba(255, 165, 0, 0.5)"); jsonWriter.WriteEndArray(); jsonWriter.WriteEndObject(); jsonWriter.WriteEndArray(); } jsonWriter.WriteEndObject(); } //query type chart { jsonWriter.WritePropertyName("queryTypeChartData"); jsonWriter.WriteStartObject(); List> queryTypes = data["queryTypes"]; //labels { jsonWriter.WritePropertyName("labels"); jsonWriter.WriteStartArray(); foreach (KeyValuePair item in queryTypes) jsonWriter.WriteValue(item.Key); jsonWriter.WriteEndArray(); } //datasets { jsonWriter.WritePropertyName("datasets"); jsonWriter.WriteStartArray(); jsonWriter.WriteStartObject(); jsonWriter.WritePropertyName("data"); jsonWriter.WriteStartArray(); foreach (KeyValuePair item in queryTypes) jsonWriter.WriteValue(item.Value); jsonWriter.WriteEndArray(); jsonWriter.WritePropertyName("backgroundColor"); jsonWriter.WriteStartArray(); jsonWriter.WriteValue("rgba(102, 153, 255, 0.5)"); jsonWriter.WriteValue("rgba(92, 184, 92, 0.5)"); jsonWriter.WriteValue("rgba(7, 7, 7, 0.5)"); jsonWriter.WriteValue("rgba(91, 192, 222, 0.5)"); jsonWriter.WriteValue("rgba(150, 150, 0, 0.5)"); jsonWriter.WriteValue("rgba(23, 162, 184, 0.5)"); jsonWriter.WriteValue("rgba(111, 84, 153, 0.5)"); jsonWriter.WriteValue("rgba(255, 165, 0, 0.5)"); jsonWriter.WriteValue("rgba(51, 122, 183, 0.5)"); jsonWriter.WriteValue("rgba(150, 150, 150, 0.5)"); jsonWriter.WriteEndArray(); jsonWriter.WriteEndObject(); jsonWriter.WriteEndArray(); } jsonWriter.WriteEndObject(); } //top clients { List> topClients = data["topClients"]; IDictionary clientIpMap = await ResolvePtrTopClientsAsync(topClients); jsonWriter.WritePropertyName("topClients"); jsonWriter.WriteStartArray(); foreach (KeyValuePair item in topClients) { jsonWriter.WriteStartObject(); jsonWriter.WritePropertyName("name"); jsonWriter.WriteValue(item.Key); if (clientIpMap.TryGetValue(item.Key, out string clientDomain) && !string.IsNullOrEmpty(clientDomain)) { jsonWriter.WritePropertyName("domain"); jsonWriter.WriteValue(clientDomain); } jsonWriter.WritePropertyName("hits"); jsonWriter.WriteValue(item.Value); jsonWriter.WriteEndObject(); } jsonWriter.WriteEndArray(); } //top domains { List> topDomains = data["topDomains"]; jsonWriter.WritePropertyName("topDomains"); jsonWriter.WriteStartArray(); foreach (KeyValuePair item in topDomains) { jsonWriter.WriteStartObject(); jsonWriter.WritePropertyName("name"); jsonWriter.WriteValue(item.Key); jsonWriter.WritePropertyName("hits"); jsonWriter.WriteValue(item.Value); jsonWriter.WriteEndObject(); } jsonWriter.WriteEndArray(); } //top blocked domains { List> topBlockedDomains = data["topBlockedDomains"]; jsonWriter.WritePropertyName("topBlockedDomains"); jsonWriter.WriteStartArray(); foreach (KeyValuePair item in topBlockedDomains) { jsonWriter.WriteStartObject(); jsonWriter.WritePropertyName("name"); jsonWriter.WriteValue(item.Key); jsonWriter.WritePropertyName("hits"); jsonWriter.WriteValue(item.Value); jsonWriter.WriteEndObject(); } jsonWriter.WriteEndArray(); } } private static void WriteChartDataSet(JsonTextWriter jsonWriter, string label, string backgroundColor, string borderColor, List> statsPerInterval) { jsonWriter.WriteStartObject(); jsonWriter.WritePropertyName("label"); jsonWriter.WriteValue(label); jsonWriter.WritePropertyName("backgroundColor"); jsonWriter.WriteValue(backgroundColor); jsonWriter.WritePropertyName("borderColor"); jsonWriter.WriteValue(borderColor); jsonWriter.WritePropertyName("borderWidth"); jsonWriter.WriteValue(2); jsonWriter.WritePropertyName("fill"); jsonWriter.WriteValue(true); jsonWriter.WritePropertyName("data"); jsonWriter.WriteStartArray(); foreach (KeyValuePair item in statsPerInterval) jsonWriter.WriteValue(item.Value); jsonWriter.WriteEndArray(); jsonWriter.WriteEndObject(); } private async Task GetTopStats(HttpListenerRequest request, JsonTextWriter jsonWriter) { string strType = request.QueryString["type"]; if (string.IsNullOrEmpty(strType)) strType = "lastHour"; string strStatsType = request.QueryString["statsType"]; if (string.IsNullOrEmpty(strStatsType)) throw new DnsWebServiceException("Parameter 'statsType' missing."); string strLimit = request.QueryString["limit"]; if (string.IsNullOrEmpty(strLimit)) strLimit = "1000"; TopStatsType statsType = (TopStatsType)Enum.Parse(typeof(TopStatsType), strStatsType, true); int limit = int.Parse(strLimit); List> topStatsData; switch (strType) { case "lastHour": topStatsData = _dnsServer.StatsManager.GetLastHourTopStats(statsType, limit); break; case "lastDay": topStatsData = _dnsServer.StatsManager.GetLastDayTopStats(statsType, limit); break; case "lastWeek": topStatsData = _dnsServer.StatsManager.GetLastWeekTopStats(statsType, limit); break; case "lastMonth": topStatsData = _dnsServer.StatsManager.GetLastMonthTopStats(statsType, limit); break; case "lastYear": topStatsData = _dnsServer.StatsManager.GetLastYearTopStats(statsType, limit); break; case "custom": string strStartDate = request.QueryString["start"]; if (string.IsNullOrEmpty(strStartDate)) throw new DnsWebServiceException("Parameter 'start' missing."); string strEndDate = request.QueryString["end"]; if (string.IsNullOrEmpty(strEndDate)) throw new DnsWebServiceException("Parameter 'end' missing."); if (!DateTime.TryParseExact(strStartDate, "yyyy-M-d", CultureInfo.InvariantCulture, DateTimeStyles.AssumeUniversal | DateTimeStyles.AdjustToUniversal, out DateTime startDate)) throw new DnsWebServiceException("Invalid start date format."); if (!DateTime.TryParseExact(strEndDate, "yyyy-M-d", CultureInfo.InvariantCulture, DateTimeStyles.AssumeUniversal | DateTimeStyles.AdjustToUniversal, out DateTime endDate)) throw new DnsWebServiceException("Invalid end date format."); if (startDate > endDate) throw new DnsWebServiceException("Start date must be less than or equal to end date."); if ((Convert.ToInt32((endDate - startDate).TotalDays) + 1) > 7) topStatsData = _dnsServer.StatsManager.GetDayWiseTopStats(startDate, endDate, statsType, limit); else topStatsData = _dnsServer.StatsManager.GetHourWiseTopStats(startDate, endDate, statsType, limit); break; default: throw new DnsWebServiceException("Unknown stats type requested: " + strType); } switch (statsType) { case TopStatsType.TopClients: { IDictionary clientIpMap = await ResolvePtrTopClientsAsync(topStatsData); jsonWriter.WritePropertyName("topClients"); jsonWriter.WriteStartArray(); foreach (KeyValuePair item in topStatsData) { jsonWriter.WriteStartObject(); jsonWriter.WritePropertyName("name"); jsonWriter.WriteValue(item.Key); if (clientIpMap.TryGetValue(item.Key, out string clientDomain) && !string.IsNullOrEmpty(clientDomain)) { jsonWriter.WritePropertyName("domain"); jsonWriter.WriteValue(clientDomain); } jsonWriter.WritePropertyName("hits"); jsonWriter.WriteValue(item.Value); jsonWriter.WriteEndObject(); } jsonWriter.WriteEndArray(); } break; case TopStatsType.TopDomains: { jsonWriter.WritePropertyName("topDomains"); jsonWriter.WriteStartArray(); foreach (KeyValuePair item in topStatsData) { jsonWriter.WriteStartObject(); jsonWriter.WritePropertyName("name"); jsonWriter.WriteValue(item.Key); jsonWriter.WritePropertyName("hits"); jsonWriter.WriteValue(item.Value); jsonWriter.WriteEndObject(); } jsonWriter.WriteEndArray(); } break; case TopStatsType.TopBlockedDomains: { jsonWriter.WritePropertyName("topBlockedDomains"); jsonWriter.WriteStartArray(); foreach (KeyValuePair item in topStatsData) { jsonWriter.WriteStartObject(); jsonWriter.WritePropertyName("name"); jsonWriter.WriteValue(item.Key); jsonWriter.WritePropertyName("hits"); jsonWriter.WriteValue(item.Value); jsonWriter.WriteEndObject(); } jsonWriter.WriteEndArray(); } break; default: throw new NotSupportedException(); } } private async Task> ResolvePtrTopClientsAsync(List> topClients) { IDictionary dhcpClientIpMap = _dhcpServer.GetAddressHostNameMap(); async Task> ResolvePtrAsync(string ip) { if (dhcpClientIpMap.TryGetValue(ip, out string dhcpDomain)) return new KeyValuePair(ip, dhcpDomain); IPAddress address = IPAddress.Parse(ip); if (IPAddress.IsLoopback(address)) return new KeyValuePair(ip, "localhost"); DnsDatagram ptrResponse = await _dnsServer.DirectQueryAsync(new DnsQuestionRecord(address, DnsClass.IN)).WithTimeout(500); if (ptrResponse.Answer.Count > 0) { IReadOnlyList ptrDomains = DnsClient.ParseResponsePTR(ptrResponse); if (ptrDomains.Count > 0) return new KeyValuePair(ip, ptrDomains[0]); } return new KeyValuePair(ip, null); } List>> resolverTasks = new List>>(); foreach (KeyValuePair item in topClients) { resolverTasks.Add(ResolvePtrAsync(item.Key)); } Dictionary result = new Dictionary(); foreach (Task> resolverTask in resolverTasks) { try { KeyValuePair ptrResult = await resolverTask; result[ptrResult.Key] = ptrResult.Value; } catch { } } return result; } #endregion #region cache api private void FlushCache(HttpListenerRequest request) { _dnsServer.CacheZoneManager.Flush(); _log.Write(GetRequestRemoteEndPoint(request), "[" + GetSession(request).Username + "] Cache was flushed."); } private void ListCachedZones(HttpListenerRequest request, JsonTextWriter jsonWriter) { string domain = request.QueryString["domain"]; if (domain == null) domain = ""; string direction = request.QueryString["direction"]; List subZones = new List(); List records = new List(); while (true) { subZones.Clear(); records.Clear(); _dnsServer.CacheZoneManager.ListSubDomains(domain, subZones); _dnsServer.CacheZoneManager.ListAllRecords(domain, records); if (records.Count > 0) break; if (subZones.Count != 1) break; if (direction == "up") { if (domain.Length == 0) break; int i = domain.IndexOf('.'); if (i < 0) domain = ""; else domain = domain.Substring(i + 1); } else if (domain.Length == 0) { domain = subZones[0]; } else { domain = subZones[0] + "." + domain; } } subZones.Sort(); jsonWriter.WritePropertyName("domain"); jsonWriter.WriteValue(domain); jsonWriter.WritePropertyName("zones"); jsonWriter.WriteStartArray(); if (domain.Length != 0) domain = "." + domain; foreach (string subZone in subZones) jsonWriter.WriteValue(subZone + domain); jsonWriter.WriteEndArray(); WriteRecordsAsJson(records, jsonWriter, false); } private void DeleteCachedZone(HttpListenerRequest request) { string domain = request.QueryString["domain"]; if (string.IsNullOrEmpty(domain)) throw new DnsWebServiceException("Parameter 'domain' missing."); if (_dnsServer.CacheZoneManager.DeleteZone(domain)) _log.Write(GetRequestRemoteEndPoint(request), "[" + GetSession(request).Username + "] Cached zone was deleted: " + domain); } #endregion #region allowed zones api private void ListAllowedZones(HttpListenerRequest request, JsonTextWriter jsonWriter) { string domain = request.QueryString["domain"]; if (domain == null) domain = ""; string direction = request.QueryString["direction"]; List subZones = new List(); List records = new List(); while (true) { subZones.Clear(); records.Clear(); _dnsServer.AllowedZoneManager.ListSubDomains(domain, subZones); _dnsServer.AllowedZoneManager.ListAllRecords(domain, records); if (records.Count > 0) break; if (subZones.Count != 1) break; if (direction == "up") { if (domain.Length == 0) break; int i = domain.IndexOf('.'); if (i < 0) domain = ""; else domain = domain.Substring(i + 1); } else if (domain.Length == 0) { domain = subZones[0]; } else { domain = subZones[0] + "." + domain; } } subZones.Sort(); jsonWriter.WritePropertyName("domain"); jsonWriter.WriteValue(domain); jsonWriter.WritePropertyName("zones"); jsonWriter.WriteStartArray(); if (domain.Length != 0) domain = "." + domain; foreach (string subZone in subZones) jsonWriter.WriteValue(subZone + domain); jsonWriter.WriteEndArray(); WriteRecordsAsJson(new List(records), jsonWriter, false); } private void ImportAllowedZones(HttpListenerRequest request) { if (!request.ContentType.StartsWith("application/x-www-form-urlencoded")) throw new DnsWebServiceException("Invalid content type. Expected application/x-www-form-urlencoded."); string formRequest; using (StreamReader sR = new StreamReader(request.InputStream, request.ContentEncoding)) { formRequest = sR.ReadToEnd(); } string[] formParts = formRequest.Split('&'); foreach (string formPart in formParts) { if (formPart.StartsWith("allowedZones=")) { string[] allowedZones = formPart.Substring(13).Split(','); bool added = false; foreach (string allowedZone in allowedZones) { if (_dnsServer.AllowedZoneManager.AllowZone(allowedZone)) added = true; } if (added) { _log.Write(GetRequestRemoteEndPoint(request), "[" + GetSession(request).Username + "] Total " + allowedZones.Length + " zones were imported into allowed zone successfully."); _dnsServer.AllowedZoneManager.SaveZoneFile(); } return; } } throw new DnsWebServiceException("Parameter 'allowedZones' missing."); } private void ExportAllowedZones(HttpListenerResponse response) { IReadOnlyList zoneInfoList = _dnsServer.AllowedZoneManager.ListZones(); response.ContentType = "text/plain"; response.AddHeader("Content-Disposition", "attachment;filename=AllowedZones.txt"); using (StreamWriter sW = new StreamWriter(new BufferedStream(response.OutputStream))) { foreach (AuthZoneInfo zoneInfo in zoneInfoList) sW.WriteLine(zoneInfo.Name); } } private void DeleteAllowedZone(HttpListenerRequest request) { string domain = request.QueryString["domain"]; if (string.IsNullOrEmpty(domain)) throw new DnsWebServiceException("Parameter 'domain' missing."); if (_dnsServer.AllowedZoneManager.DeleteZone(domain)) { _log.Write(GetRequestRemoteEndPoint(request), "[" + GetSession(request).Username + "] Allowed zone was deleted: " + domain); _dnsServer.AllowedZoneManager.SaveZoneFile(); } } private void AllowZone(HttpListenerRequest request) { string domain = request.QueryString["domain"]; if (string.IsNullOrEmpty(domain)) throw new DnsWebServiceException("Parameter 'domain' missing."); if (IPAddress.TryParse(domain, out IPAddress ipAddress)) domain = (new DnsQuestionRecord(ipAddress, DnsClass.IN)).Name; if (_dnsServer.AllowedZoneManager.AllowZone(domain)) { _log.Write(GetRequestRemoteEndPoint(request), "[" + GetSession(request).Username + "] Zone was allowed: " + domain); _dnsServer.AllowedZoneManager.SaveZoneFile(); } } #endregion #region blocked zones api private void ListBlockedZones(HttpListenerRequest request, JsonTextWriter jsonWriter) { string domain = request.QueryString["domain"]; if (domain == null) domain = ""; string direction = request.QueryString["direction"]; List subZones = new List(); List records = new List(); while (true) { subZones.Clear(); records.Clear(); _dnsServer.BlockedZoneManager.ListSubDomains(domain, subZones); _dnsServer.BlockedZoneManager.ListAllRecords(domain, records); if (records.Count > 0) break; if (subZones.Count != 1) break; if (direction == "up") { if (domain.Length == 0) break; int i = domain.IndexOf('.'); if (i < 0) domain = ""; else domain = domain.Substring(i + 1); } else if (domain.Length == 0) { domain = subZones[0]; } else { domain = subZones[0] + "." + domain; } } subZones.Sort(); jsonWriter.WritePropertyName("domain"); jsonWriter.WriteValue(domain); jsonWriter.WritePropertyName("zones"); jsonWriter.WriteStartArray(); if (domain.Length != 0) domain = "." + domain; foreach (string subZone in subZones) jsonWriter.WriteValue(subZone + domain); jsonWriter.WriteEndArray(); WriteRecordsAsJson(new List(records), jsonWriter, false); } private void ImportBlockedZones(HttpListenerRequest request) { if (!request.ContentType.StartsWith("application/x-www-form-urlencoded")) throw new DnsWebServiceException("Invalid content type. Expected application/x-www-form-urlencoded."); string formRequest; using (StreamReader sR = new StreamReader(request.InputStream, request.ContentEncoding)) { formRequest = sR.ReadToEnd(); } string[] formParts = formRequest.Split('&'); foreach (string formPart in formParts) { if (formPart.StartsWith("blockedZones=")) { string[] blockedZones = formPart.Substring(13).Split(','); bool added = false; foreach (string blockedZone in blockedZones) { if (_dnsServer.BlockedZoneManager.BlockZone(blockedZone)) added = true; } if (added) { _log.Write(GetRequestRemoteEndPoint(request), "[" + GetSession(request).Username + "] Total " + blockedZones.Length + " zones were imported into blocked zone successfully."); _dnsServer.BlockedZoneManager.SaveZoneFile(); } return; } } throw new DnsWebServiceException("Parameter 'blockedZones' missing."); } private void ExportBlockedZones(HttpListenerResponse response) { IReadOnlyList zoneInfoList = _dnsServer.BlockedZoneManager.ListZones(); response.ContentType = "text/plain"; response.AddHeader("Content-Disposition", "attachment;filename=BlockedZones.txt"); using (StreamWriter sW = new StreamWriter(new BufferedStream(response.OutputStream))) { foreach (AuthZoneInfo zoneInfo in zoneInfoList) sW.WriteLine(zoneInfo.Name); } } private void DeleteBlockedZone(HttpListenerRequest request) { string domain = request.QueryString["domain"]; if (string.IsNullOrEmpty(domain)) throw new DnsWebServiceException("Parameter 'domain' missing."); if (_dnsServer.BlockedZoneManager.DeleteZone(domain)) { _log.Write(GetRequestRemoteEndPoint(request), "[" + GetSession(request).Username + "] Blocked zone was deleted: " + domain); _dnsServer.BlockedZoneManager.SaveZoneFile(); } } private void BlockZone(HttpListenerRequest request) { string domain = request.QueryString["domain"]; if (string.IsNullOrEmpty(domain)) throw new DnsWebServiceException("Parameter 'domain' missing."); if (IPAddress.TryParse(domain, out IPAddress ipAddress)) domain = (new DnsQuestionRecord(ipAddress, DnsClass.IN)).Name; if (_dnsServer.BlockedZoneManager.BlockZone(domain)) { _log.Write(GetRequestRemoteEndPoint(request), "[" + GetSession(request).Username + "] Domain was added to blocked zone: " + domain); _dnsServer.BlockedZoneManager.SaveZoneFile(); } } #endregion #region zones api private void ListZones(JsonTextWriter jsonWriter) { List zones = _dnsServer.AuthZoneManager.ListZones(); zones.Sort(); jsonWriter.WritePropertyName("zones"); jsonWriter.WriteStartArray(); foreach (AuthZoneInfo zone in zones) { jsonWriter.WriteStartObject(); jsonWriter.WritePropertyName("name"); jsonWriter.WriteValue(zone.Name); jsonWriter.WritePropertyName("type"); jsonWriter.WriteValue(zone.Type.ToString()); switch (zone.Type) { case AuthZoneType.Primary: jsonWriter.WritePropertyName("internal"); jsonWriter.WriteValue(zone.Internal); break; case AuthZoneType.Secondary: case AuthZoneType.Stub: jsonWriter.WritePropertyName("expiry"); jsonWriter.WriteValue(zone.Expiry); jsonWriter.WritePropertyName("isExpired"); jsonWriter.WriteValue(zone.IsExpired); break; } jsonWriter.WritePropertyName("disabled"); jsonWriter.WriteValue(zone.Disabled); jsonWriter.WriteEndObject(); } jsonWriter.WriteEndArray(); } private async Task CreateZoneAsync(HttpListenerRequest request, JsonTextWriter jsonWriter) { string domain = request.QueryString["domain"]; if (string.IsNullOrEmpty(domain)) throw new DnsWebServiceException("Parameter 'domain' missing."); if (domain.Contains("*")) throw new DnsWebServiceException("Domain name for a zone cannot contain wildcard character."); if (IPAddress.TryParse(domain, out IPAddress ipAddress)) { domain = new DnsQuestionRecord(ipAddress, DnsClass.IN).Name.ToLower(); } else if (domain.Contains("/")) { string[] parts = domain.Split('/'); if ((parts.Length == 2) && IPAddress.TryParse(parts[0], out ipAddress) && int.TryParse(parts[1], out int subnetMaskWidth)) domain = Zone.GetReverseZone(ipAddress, subnetMaskWidth); } else if (domain.EndsWith(".")) { domain = domain.Substring(0, domain.Length - 1); } AuthZoneType type = AuthZoneType.Primary; string strType = request.QueryString["type"]; if (!string.IsNullOrEmpty(strType)) type = (AuthZoneType)Enum.Parse(typeof(AuthZoneType), strType, true); switch (type) { case AuthZoneType.Primary: if (_dnsServer.AuthZoneManager.CreatePrimaryZone(domain, _dnsServer.ServerDomain, false) == null) throw new DnsWebServiceException("Zone already exists: " + domain); _log.Write(GetRequestRemoteEndPoint(request), "[" + GetSession(request).Username + "] Authoritative primary zone was created: " + domain); _dnsServer.AuthZoneManager.SaveZoneFile(domain); break; case AuthZoneType.Secondary: { string primaryNameServerAddresses = request.QueryString["primaryNameServerAddresses"]; if (string.IsNullOrEmpty(primaryNameServerAddresses)) primaryNameServerAddresses = null; DnsTransportProtocol zoneTransferProtocol; string strZoneTransferProtocol = request.QueryString["zoneTransferProtocol"]; if (string.IsNullOrEmpty(strZoneTransferProtocol)) zoneTransferProtocol = DnsTransportProtocol.Tcp; else zoneTransferProtocol = Enum.Parse(strZoneTransferProtocol, true); string tsigKeyName = request.QueryString["tsigKeyName"]; if (string.IsNullOrEmpty(tsigKeyName)) tsigKeyName = null; if (await _dnsServer.AuthZoneManager.CreateSecondaryZoneAsync(domain, primaryNameServerAddresses, zoneTransferProtocol, tsigKeyName) == null) throw new DnsWebServiceException("Zone already exists: " + domain); _log.Write(GetRequestRemoteEndPoint(request), "[" + GetSession(request).Username + "] Authoritative secondary zone was created: " + domain); _dnsServer.AuthZoneManager.SaveZoneFile(domain); } break; case AuthZoneType.Stub: { string strPrimaryNameServerAddresses = request.QueryString["primaryNameServerAddresses"]; if (string.IsNullOrEmpty(strPrimaryNameServerAddresses)) strPrimaryNameServerAddresses = null; if (await _dnsServer.AuthZoneManager.CreateStubZoneAsync(domain, strPrimaryNameServerAddresses) == null) throw new DnsWebServiceException("Zone already exists: " + domain); _log.Write(GetRequestRemoteEndPoint(request), "[" + GetSession(request).Username + "] Stub zone was created: " + domain); _dnsServer.AuthZoneManager.SaveZoneFile(domain); } break; case AuthZoneType.Forwarder: { DnsTransportProtocol forwarderProtocol = DnsTransportProtocol.Udp; string strForwarderProtocol = request.QueryString["protocol"]; if (!string.IsNullOrEmpty(strForwarderProtocol)) forwarderProtocol = (DnsTransportProtocol)Enum.Parse(typeof(DnsTransportProtocol), strForwarderProtocol, true); string strForwarder = request.QueryString["forwarder"]; if (string.IsNullOrEmpty(strForwarder)) throw new DnsWebServiceException("Parameter 'forwarder' missing."); if (_dnsServer.AuthZoneManager.CreateForwarderZone(domain, forwarderProtocol, strForwarder) == null) throw new DnsWebServiceException("Zone already exists: " + domain); _log.Write(GetRequestRemoteEndPoint(request), "[" + GetSession(request).Username + "] Forwarder zone was created: " + domain); _dnsServer.AuthZoneManager.SaveZoneFile(domain); } break; default: throw new NotSupportedException("Zone type not supported."); } //delete cache for this zone to allow rebuilding cache data as needed by stub or forwarder zones _dnsServer.CacheZoneManager.DeleteZone(domain); jsonWriter.WritePropertyName("domain"); jsonWriter.WriteValue(string.IsNullOrEmpty(domain) ? "." : domain); } private void DeleteZone(HttpListenerRequest request) { string domain = request.QueryString["domain"]; if (string.IsNullOrEmpty(domain)) throw new DnsWebServiceException("Parameter 'domain' missing."); domain = domain.TrimEnd('.'); AuthZoneInfo zoneInfo = _dnsServer.AuthZoneManager.GetAuthZoneInfo(domain); if (zoneInfo == null) throw new DnsWebServiceException("Zone '" + domain + "' was not found."); if (zoneInfo.Internal) throw new DnsWebServiceException("Access was denied to manage internal DNS Server zone."); if (!_dnsServer.AuthZoneManager.DeleteZone(domain)) throw new DnsWebServiceException("Zone '" + domain + "' was not found."); _log.Write(GetRequestRemoteEndPoint(request), "[" + GetSession(request).Username + "] " + zoneInfo.Type.ToString() + " zone was deleted: " + domain); _dnsServer.AuthZoneManager.DeleteZoneFile(zoneInfo.Name); } private void EnableZone(HttpListenerRequest request) { string domain = request.QueryString["domain"]; if (string.IsNullOrEmpty(domain)) throw new DnsWebServiceException("Parameter 'domain' missing."); domain = domain.TrimEnd('.'); AuthZoneInfo zoneInfo = _dnsServer.AuthZoneManager.GetAuthZoneInfo(domain); if (zoneInfo == null) throw new DnsWebServiceException("Zone '" + domain + "' was not found."); if (zoneInfo.Internal) throw new DnsWebServiceException("Access was denied to manage internal DNS Server zone."); zoneInfo.Disabled = false; _log.Write(GetRequestRemoteEndPoint(request), "[" + GetSession(request).Username + "] " + zoneInfo.Type.ToString() + " zone was enabled: " + zoneInfo.Name); _dnsServer.AuthZoneManager.SaveZoneFile(zoneInfo.Name); //delete cache for this zone to allow rebuilding cache data as needed by stub or forwarder zones _dnsServer.CacheZoneManager.DeleteZone(zoneInfo.Name); } private void DisableZone(HttpListenerRequest request) { string domain = request.QueryString["domain"]; if (string.IsNullOrEmpty(domain)) throw new DnsWebServiceException("Parameter 'domain' missing."); domain = domain.TrimEnd('.'); AuthZoneInfo zoneInfo = _dnsServer.AuthZoneManager.GetAuthZoneInfo(domain); if (zoneInfo == null) throw new DnsWebServiceException("Zone '" + domain + "' was not found."); if (zoneInfo.Internal) throw new DnsWebServiceException("Access was denied to manage internal DNS Server zone."); zoneInfo.Disabled = true; _log.Write(GetRequestRemoteEndPoint(request), "[" + GetSession(request).Username + "] " + zoneInfo.Type.ToString() + " zone was disabled: " + zoneInfo.Name); _dnsServer.AuthZoneManager.SaveZoneFile(zoneInfo.Name); } private void GetZoneOptions(HttpListenerRequest request, JsonTextWriter jsonWriter) { string domain = request.QueryString["domain"]; if (string.IsNullOrEmpty(domain)) throw new DnsWebServiceException("Parameter 'domain' missing."); domain = domain.TrimEnd('.'); AuthZoneInfo zoneInfo = _dnsServer.AuthZoneManager.GetAuthZoneInfo(domain); if (zoneInfo == null) throw new DnsWebServiceException("Zone '" + domain + "' was not found."); if (zoneInfo.Internal) throw new DnsWebServiceException("Access was denied to manage internal DNS Server zone."); jsonWriter.WritePropertyName("name"); jsonWriter.WriteValue(zoneInfo.Name); jsonWriter.WritePropertyName("type"); jsonWriter.WriteValue(zoneInfo.Type.ToString()); switch (zoneInfo.Type) { case AuthZoneType.Primary: jsonWriter.WritePropertyName("internal"); jsonWriter.WriteValue(zoneInfo.Internal); break; } jsonWriter.WritePropertyName("disabled"); jsonWriter.WriteValue(zoneInfo.Disabled); jsonWriter.WritePropertyName("zoneTransfer"); jsonWriter.WriteValue(zoneInfo.ZoneTransfer.ToString()); jsonWriter.WritePropertyName("zoneTransferNameServers"); { jsonWriter.WriteStartArray(); if (zoneInfo.ZoneTransferNameServers is not null) { foreach (IPAddress nameServer in zoneInfo.ZoneTransferNameServers) jsonWriter.WriteValue(nameServer.ToString()); } jsonWriter.WriteEndArray(); } jsonWriter.WritePropertyName("notify"); jsonWriter.WriteValue(zoneInfo.Notify.ToString()); jsonWriter.WritePropertyName("notifyNameServers"); { jsonWriter.WriteStartArray(); if (zoneInfo.NotifyNameServers is not null) { foreach (IPAddress nameServer in zoneInfo.NotifyNameServers) jsonWriter.WriteValue(nameServer.ToString()); } jsonWriter.WriteEndArray(); } jsonWriter.WritePropertyName("zoneTransferTsigKeyNames"); { jsonWriter.WriteStartArray(); if (zoneInfo.TsigKeyNames is not null) { foreach (KeyValuePair tsigKeyName in zoneInfo.TsigKeyNames) jsonWriter.WriteValue(tsigKeyName.Key); } jsonWriter.WriteEndArray(); } jsonWriter.WritePropertyName("availableTsigKeyNames"); { jsonWriter.WriteStartArray(); if (_dnsServer.TsigKeys is not null) { foreach (KeyValuePair tsigKey in _dnsServer.TsigKeys) jsonWriter.WriteValue(tsigKey.Key); } jsonWriter.WriteEndArray(); } } private void SetZoneOptions(HttpListenerRequest request) { string domain = request.QueryString["domain"]; if (string.IsNullOrEmpty(domain)) throw new DnsWebServiceException("Parameter 'domain' missing."); domain = domain.TrimEnd('.'); AuthZoneInfo zoneInfo = _dnsServer.AuthZoneManager.GetAuthZoneInfo(domain); if (zoneInfo == null) throw new DnsWebServiceException("Zone '" + domain + "' was not found."); if (zoneInfo.Internal) throw new DnsWebServiceException("Access was denied to manage internal DNS Server zone."); string strDisabled = request.QueryString["disabled"]; if (!string.IsNullOrEmpty(strDisabled)) zoneInfo.Disabled = bool.Parse(strDisabled); string strZoneTransfer = request.QueryString["zoneTransfer"]; if (!string.IsNullOrEmpty(strZoneTransfer)) zoneInfo.ZoneTransfer = Enum.Parse(strZoneTransfer, true); string strZoneTransferNameServers = request.QueryString["zoneTransferNameServers"]; if (!string.IsNullOrEmpty(strZoneTransferNameServers)) { if (strZoneTransferNameServers == "false") { zoneInfo.ZoneTransferNameServers = null; } else { string[] strNameServers = strZoneTransferNameServers.Split(new char[] { ',' }, StringSplitOptions.RemoveEmptyEntries); IPAddress[] nameServers = new IPAddress[strNameServers.Length]; for (int i = 0; i < strNameServers.Length; i++) nameServers[i] = IPAddress.Parse(strNameServers[i]); zoneInfo.ZoneTransferNameServers = nameServers; } } string strNotify = request.QueryString["notify"]; if (!string.IsNullOrEmpty(strNotify)) zoneInfo.Notify = Enum.Parse(strNotify, true); string strNotifyNameServers = request.QueryString["notifyNameServers"]; if (!string.IsNullOrEmpty(strNotifyNameServers)) { if (strNotifyNameServers == "false") { zoneInfo.NotifyNameServers = null; } else { string[] strNameServers = strNotifyNameServers.Split(new char[] { ',' }, StringSplitOptions.RemoveEmptyEntries); IPAddress[] nameServers = new IPAddress[strNameServers.Length]; for (int i = 0; i < strNameServers.Length; i++) nameServers[i] = IPAddress.Parse(strNameServers[i]); zoneInfo.NotifyNameServers = nameServers; } } string strZoneTransferTsigKeyNames = request.QueryString["zoneTransferTsigKeyNames"]; if (!string.IsNullOrEmpty(strZoneTransferTsigKeyNames)) { if (strZoneTransferTsigKeyNames == "false") { zoneInfo.TsigKeyNames = null; } else { string[] strZoneTransferTsigKeyNamesParts = strZoneTransferTsigKeyNames.Split(new char[] { ',' }, StringSplitOptions.RemoveEmptyEntries); Dictionary zoneTransferTsigKeyNames = new Dictionary(strZoneTransferTsigKeyNamesParts.Length); for (int i = 0; i < strZoneTransferTsigKeyNamesParts.Length; i++) zoneTransferTsigKeyNames.Add(strZoneTransferTsigKeyNamesParts[i].ToLower(), null); zoneInfo.TsigKeyNames = zoneTransferTsigKeyNames; } } _log.Write(GetRequestRemoteEndPoint(request), "[" + GetSession(request).Username + "] " + zoneInfo.Type.ToString() + " zone options were updated successfully: " + zoneInfo.Name); _dnsServer.AuthZoneManager.SaveZoneFile(zoneInfo.Name); } private void ResyncZone(HttpListenerRequest request) { string domain = request.QueryString["domain"]; if (string.IsNullOrEmpty(domain)) throw new DnsWebServiceException("Parameter 'domain' missing."); domain = domain.TrimEnd('.'); AuthZoneInfo zoneInfo = _dnsServer.AuthZoneManager.GetAuthZoneInfo(domain); if (zoneInfo == null) throw new DnsWebServiceException("Zone '" + domain + "' was not found."); if (zoneInfo.Internal) throw new DnsWebServiceException("Access was denied to manage internal DNS Server zone."); switch (zoneInfo.Type) { case AuthZoneType.Secondary: case AuthZoneType.Stub: zoneInfo.TriggerResync(); break; default: throw new DnsWebServiceException("Only Secondary and Stub zones support resync."); } } private void AddRecord(HttpListenerRequest request) { string domain = request.QueryString["domain"]; if (string.IsNullOrEmpty(domain)) throw new DnsWebServiceException("Parameter 'domain' missing."); domain = domain.TrimEnd('.'); AuthZoneInfo zoneInfo = _dnsServer.AuthZoneManager.GetAuthZoneInfo(domain); if (zoneInfo == null) throw new DnsWebServiceException("Zone '" + domain + "' was not found."); if (zoneInfo.Internal) throw new DnsWebServiceException("Access was denied to manage internal DNS Server zone."); string strType = request.QueryString["type"]; if (string.IsNullOrEmpty(strType)) throw new DnsWebServiceException("Parameter 'type' missing."); DnsResourceRecordType type = (DnsResourceRecordType)Enum.Parse(typeof(DnsResourceRecordType), strType); string value = request.QueryString["value"]; if (string.IsNullOrEmpty(value)) throw new DnsWebServiceException("Parameter 'value' missing."); uint ttl; string strTtl = request.QueryString["ttl"]; if (string.IsNullOrEmpty(strTtl)) ttl = 3600; else ttl = uint.Parse(strTtl); bool overwrite = false; string strOverwrite = request.QueryString["overwrite"]; if (!string.IsNullOrEmpty(strOverwrite)) overwrite = bool.Parse(strOverwrite); string comments = request.QueryString["comments"]; switch (type) { case DnsResourceRecordType.A: case DnsResourceRecordType.AAAA: { IPAddress ipAddress = IPAddress.Parse(value); bool ptr = false; string strPtr = request.QueryString["ptr"]; if (!string.IsNullOrEmpty(strPtr)) ptr = bool.Parse(strPtr); if (ptr) { string ptrDomain = Zone.GetReverseZone(ipAddress, type == DnsResourceRecordType.A ? 32 : 128); AuthZoneInfo reverseZoneInfo = _dnsServer.AuthZoneManager.GetAuthZoneInfo(ptrDomain); if (reverseZoneInfo == null) { bool createPtrZone = false; string strCreatePtrZone = request.QueryString["createPtrZone"]; if (!string.IsNullOrEmpty(strCreatePtrZone)) createPtrZone = bool.Parse(strCreatePtrZone); if (!createPtrZone) throw new DnsServerException("No reverse zone available to add PTR record."); string ptrZone = Zone.GetReverseZone(ipAddress, type == DnsResourceRecordType.A ? 24 : 64); reverseZoneInfo = _dnsServer.AuthZoneManager.CreatePrimaryZone(ptrZone, _dnsServer.ServerDomain, false); if (reverseZoneInfo == null) throw new DnsServerException("Failed to create reverse zone to add PTR record: " + ptrZone); } if (reverseZoneInfo.Internal) throw new DnsServerException("Reverse zone '" + reverseZoneInfo.Name + "' is an internal zone."); if (reverseZoneInfo.Type != AuthZoneType.Primary) throw new DnsServerException("Reverse zone '" + reverseZoneInfo.Name + "' is not a primary zone."); _dnsServer.AuthZoneManager.SetRecords(ptrDomain, DnsResourceRecordType.PTR, ttl, new DnsPTRRecord[] { new DnsPTRRecord(domain) }); _dnsServer.AuthZoneManager.SaveZoneFile(reverseZoneInfo.Name); } DnsResourceRecord newRecord; if (type == DnsResourceRecordType.A) newRecord = new DnsResourceRecord(domain, type, DnsClass.IN, ttl, new DnsARecord(ipAddress)); else newRecord = new DnsResourceRecord(domain, type, DnsClass.IN, ttl, new DnsAAAARecord(ipAddress)); if (!string.IsNullOrEmpty(comments)) newRecord.SetComments(comments); if (overwrite) _dnsServer.AuthZoneManager.SetRecord(newRecord); else _dnsServer.AuthZoneManager.AddRecord(newRecord); } break; case DnsResourceRecordType.MX: { string preference = request.QueryString["preference"]; if (string.IsNullOrEmpty(preference)) throw new DnsWebServiceException("Parameter 'preference' missing."); DnsResourceRecord newRecord = new DnsResourceRecord(domain, type, DnsClass.IN, ttl, new DnsMXRecord(ushort.Parse(preference), value.TrimEnd('.'))); if (!string.IsNullOrEmpty(comments)) newRecord.SetComments(comments); if (overwrite) _dnsServer.AuthZoneManager.SetRecord(newRecord); else _dnsServer.AuthZoneManager.AddRecord(newRecord); } break; case DnsResourceRecordType.TXT: { DnsResourceRecord newRecord = new DnsResourceRecord(domain, type, DnsClass.IN, ttl, new DnsTXTRecord(value)); if (!string.IsNullOrEmpty(comments)) newRecord.SetComments(comments); if (overwrite) _dnsServer.AuthZoneManager.SetRecord(newRecord); else _dnsServer.AuthZoneManager.AddRecord(newRecord); } break; case DnsResourceRecordType.NS: { string glueAddresses = request.QueryString["glue"]; if (string.IsNullOrEmpty(glueAddresses)) glueAddresses = null; DnsResourceRecord newRecord = new DnsResourceRecord(domain, type, DnsClass.IN, ttl, new DnsNSRecord(value.TrimEnd('.'))); if (glueAddresses != null) newRecord.SetGlueRecords(glueAddresses); if (!string.IsNullOrEmpty(comments)) newRecord.SetComments(comments); if (overwrite) _dnsServer.AuthZoneManager.SetRecord(newRecord); else _dnsServer.AuthZoneManager.AddRecord(newRecord); } break; case DnsResourceRecordType.PTR: { if (!overwrite) { IReadOnlyList existingRecords = _dnsServer.AuthZoneManager.GetRecords(domain, type); if (existingRecords.Count > 0) throw new DnsWebServiceException("Record already exists. Use overwrite option if you wish to overwrite existing records."); } DnsResourceRecord newRecord = new DnsResourceRecord(domain, type, DnsClass.IN, ttl, new DnsPTRRecord(value.TrimEnd('.'))); if (!string.IsNullOrEmpty(comments)) newRecord.SetComments(comments); _dnsServer.AuthZoneManager.SetRecord(newRecord); } break; case DnsResourceRecordType.CNAME: { if (!overwrite) { IReadOnlyList existingRecords = _dnsServer.AuthZoneManager.GetRecords(domain, type); if (existingRecords.Count > 0) throw new DnsWebServiceException("Record already exists. Use overwrite option if you wish to overwrite existing records."); } DnsResourceRecord newRecord = new DnsResourceRecord(domain, type, DnsClass.IN, ttl, new DnsCNAMERecord(value.TrimEnd('.'))); if (!string.IsNullOrEmpty(comments)) newRecord.SetComments(comments); _dnsServer.AuthZoneManager.SetRecord(newRecord); } break; case DnsResourceRecordType.SRV: { string priority = request.QueryString["priority"]; if (string.IsNullOrEmpty(priority)) throw new DnsWebServiceException("Parameter 'priority' missing."); string weight = request.QueryString["weight"]; if (string.IsNullOrEmpty(weight)) throw new DnsWebServiceException("Parameter 'weight' missing."); string port = request.QueryString["port"]; if (string.IsNullOrEmpty(port)) throw new DnsWebServiceException("Parameter 'port' missing."); DnsResourceRecord newRecord = new DnsResourceRecord(domain, type, DnsClass.IN, ttl, new DnsSRVRecord(ushort.Parse(priority), ushort.Parse(weight), ushort.Parse(port), value.TrimEnd('.'))); if (!string.IsNullOrEmpty(comments)) newRecord.SetComments(comments); if (overwrite) _dnsServer.AuthZoneManager.SetRecord(newRecord); else _dnsServer.AuthZoneManager.AddRecord(newRecord); } break; case DnsResourceRecordType.DNAME: { if (!overwrite) { IReadOnlyList existingRecords = _dnsServer.AuthZoneManager.GetRecords(domain, type); if (existingRecords.Count > 0) throw new DnsWebServiceException("Record already exists. Use overwrite option if you wish to overwrite existing records."); } DnsResourceRecord newRecord = new DnsResourceRecord(domain, type, DnsClass.IN, ttl, new DnsDNAMERecord(value.TrimEnd('.'))); if (!string.IsNullOrEmpty(comments)) newRecord.SetComments(comments); _dnsServer.AuthZoneManager.SetRecord(newRecord); } break; case DnsResourceRecordType.CAA: { string flags = request.QueryString["flags"]; if (string.IsNullOrEmpty(flags)) throw new DnsWebServiceException("Parameter 'flags' missing."); string tag = request.QueryString["tag"]; if (string.IsNullOrEmpty(tag)) throw new DnsWebServiceException("Parameter 'tag' missing."); DnsResourceRecord newRecord = new DnsResourceRecord(domain, type, DnsClass.IN, ttl, new DnsCAARecord(byte.Parse(flags), tag, value)); if (!string.IsNullOrEmpty(comments)) newRecord.SetComments(comments); if (overwrite) _dnsServer.AuthZoneManager.SetRecord(newRecord); else _dnsServer.AuthZoneManager.AddRecord(newRecord); } break; case DnsResourceRecordType.ANAME: { DnsResourceRecord newRecord = new DnsResourceRecord(domain, type, DnsClass.IN, ttl, new DnsANAMERecord(value.TrimEnd('.'))); if (!string.IsNullOrEmpty(comments)) newRecord.SetComments(comments); if (overwrite) _dnsServer.AuthZoneManager.SetRecord(newRecord); else _dnsServer.AuthZoneManager.AddRecord(newRecord); } break; case DnsResourceRecordType.FWD: { string protocol = request.QueryString["protocol"]; if (string.IsNullOrEmpty(protocol)) protocol = "Udp"; DnsResourceRecord newRecord = new DnsResourceRecord(domain, type, DnsClass.IN, 0, new DnsForwarderRecord((DnsTransportProtocol)Enum.Parse(typeof(DnsTransportProtocol), protocol, true), value)); if (!string.IsNullOrEmpty(comments)) newRecord.SetComments(comments); if (overwrite) _dnsServer.AuthZoneManager.SetRecord(newRecord); else _dnsServer.AuthZoneManager.AddRecord(newRecord); } break; case DnsResourceRecordType.APP: { string classPath = request.QueryString["classPath"]; if (string.IsNullOrEmpty(classPath)) throw new DnsWebServiceException("Parameter 'classPath' missing."); string recordData = request.QueryString["recordData"]; if (string.IsNullOrEmpty(recordData)) recordData = ""; if (!overwrite) { IReadOnlyList existingRecords = _dnsServer.AuthZoneManager.GetRecords(domain, type); if (existingRecords.Count > 0) throw new DnsWebServiceException("Record already exists. Use overwrite option if you wish to overwrite existing records."); } DnsResourceRecord newRecord = new DnsResourceRecord(domain, type, DnsClass.IN, ttl, new DnsApplicationRecord(value, classPath, recordData)); if (!string.IsNullOrEmpty(comments)) newRecord.SetComments(comments); _dnsServer.AuthZoneManager.SetRecord(newRecord); } break; default: throw new DnsWebServiceException("Type not supported for AddRecords()."); } _log.Write(GetRequestRemoteEndPoint(request), "[" + GetSession(request).Username + "] New record was added to authoritative zone {domain: " + domain + "; type: " + type + "; value: " + value + "; ttl: " + ttl + ";}"); _dnsServer.AuthZoneManager.SaveZoneFile(zoneInfo.Name); } private void GetRecords(HttpListenerRequest request, JsonTextWriter jsonWriter) { string domain = request.QueryString["domain"]; if (string.IsNullOrEmpty(domain)) throw new DnsWebServiceException("Parameter 'domain' missing."); domain = domain.TrimEnd('.'); AuthZoneInfo zoneInfo = _dnsServer.AuthZoneManager.GetAuthZoneInfo(domain); if (zoneInfo == null) throw new DnsWebServiceException("Zone '" + domain + "' was not found."); jsonWriter.WritePropertyName("zone"); jsonWriter.WriteStartObject(); jsonWriter.WritePropertyName("name"); jsonWriter.WriteValue(zoneInfo.Name); jsonWriter.WritePropertyName("type"); jsonWriter.WriteValue(zoneInfo.Type.ToString()); switch (zoneInfo.Type) { case AuthZoneType.Primary: jsonWriter.WritePropertyName("internal"); jsonWriter.WriteValue(zoneInfo.Internal); break; case AuthZoneType.Secondary: case AuthZoneType.Stub: jsonWriter.WritePropertyName("expiry"); jsonWriter.WriteValue(zoneInfo.Expiry); jsonWriter.WritePropertyName("isExpired"); jsonWriter.WriteValue(zoneInfo.IsExpired); break; } jsonWriter.WritePropertyName("disabled"); jsonWriter.WriteValue(zoneInfo.Disabled); jsonWriter.WriteEndObject(); List records = new List(); _dnsServer.AuthZoneManager.ListAllRecords(domain, records); WriteRecordsAsJson(records, jsonWriter, true); } private static void WriteRecordsAsJson(List records, JsonTextWriter jsonWriter, bool authoritativeZoneRecords) { if (records == null) { jsonWriter.WritePropertyName("records"); jsonWriter.WriteStartArray(); jsonWriter.WriteEndArray(); return; } records.Sort(); Dictionary>> groupedByDomainRecords = DnsResourceRecord.GroupRecords(records); jsonWriter.WritePropertyName("records"); jsonWriter.WriteStartArray(); foreach (KeyValuePair>> groupedByTypeRecords in groupedByDomainRecords) { foreach (KeyValuePair> groupedRecords in groupedByTypeRecords.Value) { foreach (DnsResourceRecord record in groupedRecords.Value) { jsonWriter.WriteStartObject(); if (authoritativeZoneRecords) { jsonWriter.WritePropertyName("disabled"); jsonWriter.WriteValue(record.IsDisabled()); } jsonWriter.WritePropertyName("name"); jsonWriter.WriteValue(record.Name); jsonWriter.WritePropertyName("type"); jsonWriter.WriteValue(record.Type.ToString()); jsonWriter.WritePropertyName("ttl"); if (authoritativeZoneRecords) jsonWriter.WriteValue(record.TtlValue); else jsonWriter.WriteValue(record.TTL); if (authoritativeZoneRecords) { string comments = record.GetComments(); if (!string.IsNullOrEmpty(comments)) { jsonWriter.WritePropertyName("comments"); jsonWriter.WriteValue(comments); } } jsonWriter.WritePropertyName("rData"); jsonWriter.WriteStartObject(); switch (record.Type) { case DnsResourceRecordType.A: { if (record.RDATA is DnsARecord rdata) { jsonWriter.WritePropertyName("value"); jsonWriter.WriteValue(rdata.IPAddress); } else { jsonWriter.WritePropertyName("dataType"); jsonWriter.WriteValue(record.RDATA.GetType().Name); jsonWriter.WritePropertyName("data"); jsonWriter.WriteValue(record.RDATA.ToString()); } } break; case DnsResourceRecordType.AAAA: { if (record.RDATA is DnsAAAARecord rdata) { jsonWriter.WritePropertyName("value"); jsonWriter.WriteValue(rdata.IPAddress); } else { jsonWriter.WritePropertyName("dataType"); jsonWriter.WriteValue(record.RDATA.GetType().Name); jsonWriter.WritePropertyName("data"); jsonWriter.WriteValue(record.RDATA.ToString()); } } break; case DnsResourceRecordType.SOA: { if (record.RDATA is DnsSOARecord rdata) { jsonWriter.WritePropertyName("primaryNameServer"); jsonWriter.WriteValue(rdata.PrimaryNameServer); jsonWriter.WritePropertyName("responsiblePerson"); jsonWriter.WriteValue(rdata.ResponsiblePerson); jsonWriter.WritePropertyName("serial"); jsonWriter.WriteValue(rdata.Serial); jsonWriter.WritePropertyName("refresh"); jsonWriter.WriteValue(rdata.Refresh); jsonWriter.WritePropertyName("retry"); jsonWriter.WriteValue(rdata.Retry); jsonWriter.WritePropertyName("expire"); jsonWriter.WriteValue(rdata.Expire); jsonWriter.WritePropertyName("minimum"); jsonWriter.WriteValue(rdata.Minimum); } else { jsonWriter.WritePropertyName("dataType"); jsonWriter.WriteValue(record.RDATA.GetType().Name); jsonWriter.WritePropertyName("data"); jsonWriter.WriteValue(record.RDATA.ToString()); } if (authoritativeZoneRecords) { IReadOnlyList primaryNameServers = record.GetPrimaryNameServers(); if (primaryNameServers.Count > 0) { string primaryAddresses = null; foreach (NameServerAddress primaryNameServer in primaryNameServers) { if (primaryAddresses == null) primaryAddresses = primaryNameServer.OriginalAddress; else primaryAddresses = primaryAddresses + ", " + primaryNameServer.OriginalAddress; } jsonWriter.WritePropertyName("primaryAddresses"); jsonWriter.WriteValue(primaryAddresses); } DnsResourceRecordInfo recordInfo = record.GetRecordInfo(); if (recordInfo.ZoneTransferProtocol != DnsTransportProtocol.Udp) { jsonWriter.WritePropertyName("zoneTransferProtocol"); jsonWriter.WriteValue(recordInfo.ZoneTransferProtocol.ToString()); } if (!string.IsNullOrEmpty(recordInfo.TsigKeyName)) { jsonWriter.WritePropertyName("tsigKeyName"); jsonWriter.WriteValue(recordInfo.TsigKeyName); } } } break; case DnsResourceRecordType.PTR: { if (record.RDATA is DnsPTRRecord rdata) { jsonWriter.WritePropertyName("value"); jsonWriter.WriteValue(rdata.Domain.Length == 0 ? "." : rdata.Domain); } else { jsonWriter.WritePropertyName("dataType"); jsonWriter.WriteValue(record.RDATA.GetType().Name); jsonWriter.WritePropertyName("data"); jsonWriter.WriteValue(record.RDATA.ToString()); } } break; case DnsResourceRecordType.MX: { if (record.RDATA is DnsMXRecord rdata) { jsonWriter.WritePropertyName("preference"); jsonWriter.WriteValue(rdata.Preference); jsonWriter.WritePropertyName("value"); jsonWriter.WriteValue(rdata.Exchange.Length == 0 ? "." : rdata.Exchange); IReadOnlyList glueRecords = record.GetGlueRecords(); if (glueRecords.Count > 0) { string glue = null; foreach (DnsResourceRecord glueRecord in glueRecords) { if (glue == null) glue = glueRecord.RDATA.ToString(); else glue = glue + ", " + glueRecord.RDATA.ToString(); } jsonWriter.WritePropertyName("glue"); jsonWriter.WriteValue(glue); } } else { jsonWriter.WritePropertyName("dataType"); jsonWriter.WriteValue(record.RDATA.GetType().Name); jsonWriter.WritePropertyName("data"); jsonWriter.WriteValue(record.RDATA.ToString()); } } break; case DnsResourceRecordType.TXT: { if (record.RDATA is DnsTXTRecord rdata) { jsonWriter.WritePropertyName("value"); jsonWriter.WriteValue(rdata.Text); } else { jsonWriter.WritePropertyName("dataType"); jsonWriter.WriteValue(record.RDATA.GetType().Name); jsonWriter.WritePropertyName("data"); jsonWriter.WriteValue(record.RDATA.ToString()); } } break; case DnsResourceRecordType.NS: { if (record.RDATA is DnsNSRecord rdata) { jsonWriter.WritePropertyName("value"); jsonWriter.WriteValue(rdata.NameServer.Length == 0 ? "." : rdata.NameServer); IReadOnlyList glueRecords = record.GetGlueRecords(); if (glueRecords.Count > 0) { string glue = null; foreach (DnsResourceRecord glueRecord in glueRecords) { if (glue == null) glue = glueRecord.RDATA.ToString(); else glue = glue + ", " + glueRecord.RDATA.ToString(); } jsonWriter.WritePropertyName("glue"); jsonWriter.WriteValue(glue); } } else { jsonWriter.WritePropertyName("dataType"); jsonWriter.WriteValue(record.RDATA.GetType().Name); jsonWriter.WritePropertyName("data"); jsonWriter.WriteValue(record.RDATA.ToString()); } } break; case DnsResourceRecordType.CNAME: { if (record.RDATA is DnsCNAMERecord rdata) { jsonWriter.WritePropertyName("value"); jsonWriter.WriteValue(rdata.Domain.Length == 0 ? "." : rdata.Domain); } else { jsonWriter.WritePropertyName("dataType"); jsonWriter.WriteValue(record.RDATA.GetType().Name); jsonWriter.WritePropertyName("data"); jsonWriter.WriteValue(record.RDATA.ToString()); } } break; case DnsResourceRecordType.SRV: { if (record.RDATA is DnsSRVRecord rdata) { jsonWriter.WritePropertyName("priority"); jsonWriter.WriteValue(rdata.Priority); jsonWriter.WritePropertyName("weight"); jsonWriter.WriteValue(rdata.Weight); jsonWriter.WritePropertyName("port"); jsonWriter.WriteValue(rdata.Port); jsonWriter.WritePropertyName("value"); jsonWriter.WriteValue(rdata.Target.Length == 0 ? "." : rdata.Target); IReadOnlyList glueRecords = record.GetGlueRecords(); if (glueRecords.Count > 0) { string glue = null; foreach (DnsResourceRecord glueRecord in glueRecords) { if (glue == null) glue = glueRecord.RDATA.ToString(); else glue = glue + ", " + glueRecord.RDATA.ToString(); } jsonWriter.WritePropertyName("glue"); jsonWriter.WriteValue(glue); } } else { jsonWriter.WritePropertyName("dataType"); jsonWriter.WriteValue(record.RDATA.GetType().Name); jsonWriter.WritePropertyName("data"); jsonWriter.WriteValue(record.RDATA.ToString()); } } break; case DnsResourceRecordType.DNAME: { if (record.RDATA is DnsDNAMERecord rdata) { jsonWriter.WritePropertyName("value"); jsonWriter.WriteValue(rdata.Domain.Length == 0 ? "." : rdata.Domain); } else { jsonWriter.WritePropertyName("dataType"); jsonWriter.WriteValue(record.RDATA.GetType().Name); jsonWriter.WritePropertyName("data"); jsonWriter.WriteValue(record.RDATA.ToString()); } } break; case DnsResourceRecordType.CAA: { if (record.RDATA is DnsCAARecord rdata) { jsonWriter.WritePropertyName("flags"); jsonWriter.WriteValue(rdata.Flags); jsonWriter.WritePropertyName("tag"); jsonWriter.WriteValue(rdata.Tag); jsonWriter.WritePropertyName("value"); jsonWriter.WriteValue(rdata.Value); } else { jsonWriter.WritePropertyName("dataType"); jsonWriter.WriteValue(record.RDATA.GetType().Name); jsonWriter.WritePropertyName("data"); jsonWriter.WriteValue(record.RDATA.ToString()); } } break; case DnsResourceRecordType.ANAME: { if (record.RDATA is DnsANAMERecord rdata) { jsonWriter.WritePropertyName("value"); jsonWriter.WriteValue(rdata.Domain.Length == 0 ? "." : rdata.Domain); } else { jsonWriter.WritePropertyName("dataType"); jsonWriter.WriteValue(record.RDATA.GetType().Name); jsonWriter.WritePropertyName("data"); jsonWriter.WriteValue(record.RDATA.ToString()); } } break; case DnsResourceRecordType.FWD: { if (record.RDATA is DnsForwarderRecord rdata) { jsonWriter.WritePropertyName("protocol"); jsonWriter.WriteValue(rdata.Protocol.ToString()); jsonWriter.WritePropertyName("value"); jsonWriter.WriteValue(rdata.Forwarder); } } break; case DnsResourceRecordType.APP: { if (record.RDATA is DnsApplicationRecord rdata) { jsonWriter.WritePropertyName("value"); jsonWriter.WriteValue(rdata.AppName); jsonWriter.WritePropertyName("classPath"); jsonWriter.WriteValue(rdata.ClassPath); jsonWriter.WritePropertyName("data"); jsonWriter.WriteValue(rdata.Data); } } break; default: { jsonWriter.WritePropertyName("value"); using (MemoryStream mS = new MemoryStream()) { record.RDATA.WriteTo(mS, new List()); jsonWriter.WriteValue(Convert.ToBase64String(mS.ToArray())); } } break; } jsonWriter.WriteEndObject(); jsonWriter.WriteEndObject(); } } } jsonWriter.WriteEndArray(); } private void DeleteRecord(HttpListenerRequest request) { string domain = request.QueryString["domain"]; if (string.IsNullOrEmpty(domain)) throw new DnsWebServiceException("Parameter 'domain' missing."); domain = domain.TrimEnd('.'); AuthZoneInfo zoneInfo = _dnsServer.AuthZoneManager.GetAuthZoneInfo(domain); if (zoneInfo == null) throw new DnsWebServiceException("Zone '" + domain + "' was not found."); if (zoneInfo.Internal) throw new DnsWebServiceException("Access was denied to manage internal DNS Server zone."); string strType = request.QueryString["type"]; if (string.IsNullOrEmpty(strType)) throw new DnsWebServiceException("Parameter 'type' missing."); DnsResourceRecordType type = (DnsResourceRecordType)Enum.Parse(typeof(DnsResourceRecordType), strType); string value = request.QueryString["value"]; if (string.IsNullOrEmpty(value)) throw new DnsWebServiceException("Parameter 'value' missing."); switch (type) { case DnsResourceRecordType.A: case DnsResourceRecordType.AAAA: { IPAddress address = IPAddress.Parse(value); if (type == DnsResourceRecordType.A) _dnsServer.AuthZoneManager.DeleteRecord(domain, type, new DnsARecord(address)); else _dnsServer.AuthZoneManager.DeleteRecord(domain, type, new DnsAAAARecord(address)); string ptrDomain = Zone.GetReverseZone(address, type == DnsResourceRecordType.A ? 32 : 128); AuthZoneInfo reverseZoneInfo = _dnsServer.AuthZoneManager.GetAuthZoneInfo(ptrDomain); if ((reverseZoneInfo != null) && !reverseZoneInfo.Internal && (reverseZoneInfo.Type == AuthZoneType.Primary)) { IReadOnlyList ptrRecords = _dnsServer.AuthZoneManager.QueryRecords(ptrDomain, DnsResourceRecordType.PTR); if (ptrRecords.Count > 0) { foreach (DnsResourceRecord ptrRecord in ptrRecords) { if ((ptrRecord.RDATA as DnsPTRRecord).Domain.Equals(domain, StringComparison.OrdinalIgnoreCase)) { //delete PTR record and save reverse zone _dnsServer.AuthZoneManager.DeleteRecord(ptrDomain, DnsResourceRecordType.PTR, ptrRecord.RDATA); _dnsServer.AuthZoneManager.SaveZoneFile(reverseZoneInfo.Name); break; } } } } } break; case DnsResourceRecordType.MX: _dnsServer.AuthZoneManager.DeleteRecord(domain, type, new DnsMXRecord(0, value)); break; case DnsResourceRecordType.TXT: _dnsServer.AuthZoneManager.DeleteRecord(domain, type, new DnsTXTRecord(value)); break; case DnsResourceRecordType.NS: _dnsServer.AuthZoneManager.DeleteRecord(domain, type, new DnsNSRecord(value)); break; case DnsResourceRecordType.ANAME: _dnsServer.AuthZoneManager.DeleteRecord(domain, type, new DnsANAMERecord(value)); break; case DnsResourceRecordType.CNAME: case DnsResourceRecordType.DNAME: case DnsResourceRecordType.PTR: case DnsResourceRecordType.APP: _dnsServer.AuthZoneManager.DeleteRecords(domain, type); break; case DnsResourceRecordType.SRV: { string port = request.QueryString["port"]; if (string.IsNullOrEmpty(port)) throw new DnsWebServiceException("Parameter 'port' missing."); _dnsServer.AuthZoneManager.DeleteRecord(domain, type, new DnsSRVRecord(0, 0, ushort.Parse(port), value)); } break; case DnsResourceRecordType.CAA: { string flags = request.QueryString["flags"]; if (string.IsNullOrEmpty(flags)) throw new DnsWebServiceException("Parameter 'flags' missing."); string tag = request.QueryString["tag"]; if (string.IsNullOrEmpty(tag)) throw new DnsWebServiceException("Parameter 'tag' missing."); _dnsServer.AuthZoneManager.DeleteRecord(domain, type, new DnsCAARecord(byte.Parse(flags), tag, value)); } break; case DnsResourceRecordType.FWD: { string strProtocol = request.QueryString["protocol"]; if (string.IsNullOrEmpty(strProtocol)) strProtocol = "Udp"; _dnsServer.AuthZoneManager.DeleteRecord(domain, type, new DnsForwarderRecord((DnsTransportProtocol)Enum.Parse(typeof(DnsTransportProtocol), strProtocol, true), value)); } break; default: throw new DnsWebServiceException("Type not supported for DeleteRecord()."); } _log.Write(GetRequestRemoteEndPoint(request), "[" + GetSession(request).Username + "] Record was deleted from authoritative zone {domain: " + domain + "; type: " + type + "; value: " + value + ";}"); _dnsServer.AuthZoneManager.SaveZoneFile(zoneInfo.Name); } private void UpdateRecord(HttpListenerRequest request) { string strType = request.QueryString["type"]; if (string.IsNullOrEmpty(strType)) throw new DnsWebServiceException("Parameter 'type' missing."); DnsResourceRecordType type = (DnsResourceRecordType)Enum.Parse(typeof(DnsResourceRecordType), strType); string domain = request.QueryString["domain"]; if (string.IsNullOrEmpty(domain)) throw new DnsWebServiceException("Parameter 'domain' missing."); domain = domain.TrimEnd('.'); AuthZoneInfo zoneInfo = _dnsServer.AuthZoneManager.GetAuthZoneInfo(domain); if (zoneInfo == null) throw new DnsWebServiceException("Zone '" + domain + "' was not found."); if (zoneInfo.Internal) throw new DnsWebServiceException("Access was denied to manage internal DNS Server zone."); string newDomain = request.QueryString["newDomain"]; if (string.IsNullOrEmpty(newDomain)) newDomain = domain; newDomain = newDomain.TrimEnd('.'); uint ttl; string strTtl = request.QueryString["ttl"]; if (string.IsNullOrEmpty(strTtl)) ttl = 3600; else ttl = uint.Parse(strTtl); string value = request.QueryString["value"]; string newValue = request.QueryString["newValue"]; if (string.IsNullOrEmpty(newValue)) newValue = value; bool disable = false; string strDisable = request.QueryString["disable"]; if (!string.IsNullOrEmpty(strDisable)) disable = bool.Parse(strDisable); string comments = request.QueryString["comments"]; switch (type) { case DnsResourceRecordType.A: case DnsResourceRecordType.AAAA: { IPAddress oldIpAddress = IPAddress.Parse(value); IPAddress newIpAddress = IPAddress.Parse(newValue); bool ptr = false; string strPtr = request.QueryString["ptr"]; if (!string.IsNullOrEmpty(strPtr)) ptr = bool.Parse(strPtr); if (ptr) { string ptrDomain = Zone.GetReverseZone(newIpAddress, type == DnsResourceRecordType.A ? 32 : 128); AuthZoneInfo reverseZoneInfo = _dnsServer.AuthZoneManager.GetAuthZoneInfo(ptrDomain); if (reverseZoneInfo == null) { bool createPtrZone = false; string strCreatePtrZone = request.QueryString["createPtrZone"]; if (!string.IsNullOrEmpty(strCreatePtrZone)) createPtrZone = bool.Parse(strCreatePtrZone); if (!createPtrZone) throw new DnsServerException("No reverse zone available to add PTR record."); string ptrZone = Zone.GetReverseZone(newIpAddress, type == DnsResourceRecordType.A ? 24 : 64); reverseZoneInfo = _dnsServer.AuthZoneManager.CreatePrimaryZone(ptrZone, _dnsServer.ServerDomain, false); if (reverseZoneInfo == null) throw new DnsServerException("Failed to create reverse zone to add PTR record: " + ptrZone); } if (reverseZoneInfo.Internal) throw new DnsServerException("Reverse zone '" + reverseZoneInfo.Name + "' is an internal zone."); if (reverseZoneInfo.Type != AuthZoneType.Primary) throw new DnsServerException("Reverse zone '" + reverseZoneInfo.Name + "' is not a primary zone."); string oldPtrDomain = Zone.GetReverseZone(oldIpAddress, type == DnsResourceRecordType.A ? 32 : 128); AuthZoneInfo oldReverseZoneInfo = _dnsServer.AuthZoneManager.GetAuthZoneInfo(oldPtrDomain); if ((oldReverseZoneInfo != null) && !oldReverseZoneInfo.Internal && (oldReverseZoneInfo.Type == AuthZoneType.Primary)) { //delete old PTR record if any and save old reverse zone _dnsServer.AuthZoneManager.DeleteRecords(oldPtrDomain, DnsResourceRecordType.PTR); _dnsServer.AuthZoneManager.SaveZoneFile(oldReverseZoneInfo.Name); } //add new PTR record and save reverse zone _dnsServer.AuthZoneManager.SetRecords(ptrDomain, DnsResourceRecordType.PTR, ttl, new DnsPTRRecord[] { new DnsPTRRecord(domain) }); _dnsServer.AuthZoneManager.SaveZoneFile(reverseZoneInfo.Name); } DnsResourceRecord oldRecord; DnsResourceRecord newRecord; if (type == DnsResourceRecordType.A) { oldRecord = new DnsResourceRecord(domain, type, DnsClass.IN, 0, new DnsARecord(oldIpAddress)); newRecord = new DnsResourceRecord(newDomain, type, DnsClass.IN, ttl, new DnsARecord(newIpAddress)); } else { oldRecord = new DnsResourceRecord(domain, type, DnsClass.IN, 0, new DnsAAAARecord(oldIpAddress)); newRecord = new DnsResourceRecord(newDomain, type, DnsClass.IN, ttl, new DnsAAAARecord(newIpAddress)); } if (disable) newRecord.Disable(); if (!string.IsNullOrEmpty(comments)) newRecord.SetComments(comments); _dnsServer.AuthZoneManager.UpdateRecord(oldRecord, newRecord); } break; case DnsResourceRecordType.MX: { string preference = request.QueryString["preference"]; if (string.IsNullOrEmpty(preference)) preference = "1"; DnsResourceRecord oldRecord = new DnsResourceRecord(domain, type, DnsClass.IN, 0, new DnsMXRecord(0, value)); DnsResourceRecord newRecord = new DnsResourceRecord(newDomain, type, DnsClass.IN, ttl, new DnsMXRecord(ushort.Parse(preference), newValue.TrimEnd('.'))); if (disable) newRecord.Disable(); if (!string.IsNullOrEmpty(comments)) newRecord.SetComments(comments); _dnsServer.AuthZoneManager.UpdateRecord(oldRecord, newRecord); } break; case DnsResourceRecordType.TXT: { DnsResourceRecord oldRecord = new DnsResourceRecord(domain, type, DnsClass.IN, 0, new DnsTXTRecord(value)); DnsResourceRecord newRecord = new DnsResourceRecord(newDomain, type, DnsClass.IN, ttl, new DnsTXTRecord(newValue)); if (disable) newRecord.Disable(); if (!string.IsNullOrEmpty(comments)) newRecord.SetComments(comments); _dnsServer.AuthZoneManager.UpdateRecord(oldRecord, newRecord); } break; case DnsResourceRecordType.NS: { DnsResourceRecord oldRecord = new DnsResourceRecord(domain, type, DnsClass.IN, 0, new DnsNSRecord(value)); DnsResourceRecord newRecord = new DnsResourceRecord(newDomain, type, DnsClass.IN, ttl, new DnsNSRecord(newValue.TrimEnd('.'))); if (disable) newRecord.Disable(); if (!string.IsNullOrEmpty(comments)) newRecord.SetComments(comments); string glueAddresses = request.QueryString["glue"]; if (!string.IsNullOrEmpty(glueAddresses)) newRecord.SetGlueRecords(glueAddresses); _dnsServer.AuthZoneManager.UpdateRecord(oldRecord, newRecord); } break; case DnsResourceRecordType.SOA: { string primaryNameServer = request.QueryString["primaryNameServer"]; if (string.IsNullOrEmpty(primaryNameServer)) throw new DnsWebServiceException("Parameter 'primaryNameServer' missing."); string responsiblePerson = request.QueryString["responsiblePerson"]; if (string.IsNullOrEmpty(responsiblePerson)) throw new DnsWebServiceException("Parameter 'responsiblePerson' missing."); string serial = request.QueryString["serial"]; if (string.IsNullOrEmpty(serial)) throw new DnsWebServiceException("Parameter 'serial' missing."); string refresh = request.QueryString["refresh"]; if (string.IsNullOrEmpty(refresh)) throw new DnsWebServiceException("Parameter 'refresh' missing."); string retry = request.QueryString["retry"]; if (string.IsNullOrEmpty(retry)) throw new DnsWebServiceException("Parameter 'retry' missing."); string expire = request.QueryString["expire"]; if (string.IsNullOrEmpty(expire)) throw new DnsWebServiceException("Parameter 'expire' missing."); string minimum = request.QueryString["minimum"]; if (string.IsNullOrEmpty(minimum)) throw new DnsWebServiceException("Parameter 'minimum' missing."); DnsResourceRecord newSoaRecord = new DnsResourceRecord(domain, type, DnsClass.IN, ttl, new DnsSOARecord(primaryNameServer, responsiblePerson, uint.Parse(serial), uint.Parse(refresh), uint.Parse(retry), uint.Parse(expire), uint.Parse(minimum))); switch (zoneInfo.Type) { case AuthZoneType.Secondary: case AuthZoneType.Stub: string primaryAddresses = request.QueryString["primaryAddresses"]; if (!string.IsNullOrEmpty(primaryAddresses)) newSoaRecord.SetPrimaryNameServers(primaryAddresses); break; } if (zoneInfo.Type == AuthZoneType.Secondary) { DnsResourceRecordInfo recordInfo = newSoaRecord.GetRecordInfo(); string zoneTransferProtocol = request.QueryString["zoneTransferProtocol"]; if (string.IsNullOrEmpty(zoneTransferProtocol)) recordInfo.ZoneTransferProtocol = DnsTransportProtocol.Tcp; else recordInfo.ZoneTransferProtocol = Enum.Parse(zoneTransferProtocol, true); string tsigKeyName = request.QueryString["tsigKeyName"]; if (!string.IsNullOrEmpty(tsigKeyName)) recordInfo.TsigKeyName = tsigKeyName; } if (!string.IsNullOrEmpty(comments)) newSoaRecord.SetComments(comments); _dnsServer.AuthZoneManager.SetRecord(newSoaRecord); } break; case DnsResourceRecordType.PTR: { DnsResourceRecord oldRecord = new DnsResourceRecord(domain, type, DnsClass.IN, 0, new DnsPTRRecord(value)); DnsResourceRecord newRecord = new DnsResourceRecord(newDomain, type, DnsClass.IN, ttl, new DnsPTRRecord(newValue.TrimEnd('.'))); if (disable) newRecord.Disable(); if (!string.IsNullOrEmpty(comments)) newRecord.SetComments(comments); _dnsServer.AuthZoneManager.UpdateRecord(oldRecord, newRecord); } break; case DnsResourceRecordType.CNAME: { DnsResourceRecord oldRecord = new DnsResourceRecord(domain, type, DnsClass.IN, 0, new DnsCNAMERecord(value)); DnsResourceRecord newRecord = new DnsResourceRecord(newDomain, type, DnsClass.IN, ttl, new DnsCNAMERecord(newValue.TrimEnd('.'))); if (disable) newRecord.Disable(); if (!string.IsNullOrEmpty(comments)) newRecord.SetComments(comments); _dnsServer.AuthZoneManager.UpdateRecord(oldRecord, newRecord); } break; case DnsResourceRecordType.SRV: { string port = request.QueryString["port"]; if (string.IsNullOrEmpty(port)) throw new DnsWebServiceException("Parameter 'port' missing."); string priority = request.QueryString["priority"]; if (string.IsNullOrEmpty(priority)) throw new DnsWebServiceException("Parameter 'priority' missing."); string weight = request.QueryString["weight"]; if (string.IsNullOrEmpty(weight)) throw new DnsWebServiceException("Parameter 'weight' missing."); string newPort = request.QueryString["newPort"]; if (string.IsNullOrEmpty(newPort)) newPort = port; DnsResourceRecord oldRecord = new DnsResourceRecord(domain, type, DnsClass.IN, 0, new DnsSRVRecord(0, 0, ushort.Parse(port), value)); DnsResourceRecord newRecord = new DnsResourceRecord(newDomain, type, DnsClass.IN, ttl, new DnsSRVRecord(ushort.Parse(priority), ushort.Parse(weight), ushort.Parse(newPort), newValue.TrimEnd('.'))); if (disable) newRecord.Disable(); if (!string.IsNullOrEmpty(comments)) newRecord.SetComments(comments); _dnsServer.AuthZoneManager.UpdateRecord(oldRecord, newRecord); } break; case DnsResourceRecordType.DNAME: { DnsResourceRecord oldRecord = new DnsResourceRecord(domain, type, DnsClass.IN, 0, new DnsDNAMERecord(value)); DnsResourceRecord newRecord = new DnsResourceRecord(newDomain, type, DnsClass.IN, ttl, new DnsDNAMERecord(newValue.TrimEnd('.'))); if (disable) newRecord.Disable(); if (!string.IsNullOrEmpty(comments)) newRecord.SetComments(comments); _dnsServer.AuthZoneManager.UpdateRecord(oldRecord, newRecord); } break; case DnsResourceRecordType.CAA: { string flags = request.QueryString["flags"]; if (string.IsNullOrEmpty(flags)) throw new DnsWebServiceException("Parameter 'flags' missing."); string tag = request.QueryString["tag"]; if (string.IsNullOrEmpty(tag)) throw new DnsWebServiceException("Parameter 'tag' missing."); string newFlags = request.QueryString["newFlags"]; if (string.IsNullOrEmpty(newFlags)) newFlags = flags; string newTag = request.QueryString["newTag"]; if (string.IsNullOrEmpty(newTag)) newTag = tag; DnsResourceRecord oldRecord = new DnsResourceRecord(domain, type, DnsClass.IN, 0, new DnsCAARecord(byte.Parse(flags), tag, value)); DnsResourceRecord newRecord = new DnsResourceRecord(newDomain, type, DnsClass.IN, ttl, new DnsCAARecord(byte.Parse(newFlags), newTag, newValue)); if (disable) newRecord.Disable(); if (!string.IsNullOrEmpty(comments)) newRecord.SetComments(comments); _dnsServer.AuthZoneManager.UpdateRecord(oldRecord, newRecord); } break; case DnsResourceRecordType.ANAME: { DnsResourceRecord oldRecord = new DnsResourceRecord(domain, type, DnsClass.IN, 0, new DnsANAMERecord(value)); DnsResourceRecord newRecord = new DnsResourceRecord(newDomain, type, DnsClass.IN, ttl, new DnsANAMERecord(newValue.TrimEnd('.'))); if (disable) newRecord.Disable(); if (!string.IsNullOrEmpty(comments)) newRecord.SetComments(comments); _dnsServer.AuthZoneManager.UpdateRecord(oldRecord, newRecord); } break; case DnsResourceRecordType.FWD: { string strProtocol = request.QueryString["protocol"]; if (string.IsNullOrEmpty(strProtocol)) strProtocol = "Udp"; DnsTransportProtocol protocol = (DnsTransportProtocol)Enum.Parse(typeof(DnsTransportProtocol), strProtocol, true); DnsResourceRecord oldRecord = new DnsResourceRecord(domain, type, DnsClass.IN, 0, new DnsForwarderRecord(protocol, value)); DnsResourceRecord newRecord = new DnsResourceRecord(newDomain, type, DnsClass.IN, ttl, new DnsForwarderRecord(protocol, newValue)); if (disable) newRecord.Disable(); if (!string.IsNullOrEmpty(comments)) newRecord.SetComments(comments); _dnsServer.AuthZoneManager.UpdateRecord(oldRecord, newRecord); } break; case DnsResourceRecordType.APP: { string classPath = request.QueryString["classPath"]; if (string.IsNullOrEmpty(classPath)) throw new DnsWebServiceException("Parameter 'classPath' missing."); string recordData = request.QueryString["recordData"]; if (string.IsNullOrEmpty(recordData)) recordData = ""; DnsResourceRecord oldRecord = new DnsResourceRecord(domain, type, DnsClass.IN, 0, new DnsApplicationRecord(value, classPath, recordData)); DnsResourceRecord newRecord = new DnsResourceRecord(newDomain, type, DnsClass.IN, ttl, new DnsApplicationRecord(newValue, classPath, recordData)); if (disable) newRecord.Disable(); if (!string.IsNullOrEmpty(comments)) newRecord.SetComments(comments); _dnsServer.AuthZoneManager.UpdateRecord(oldRecord, newRecord); } break; default: throw new DnsWebServiceException("Type not supported for UpdateRecords()."); } _log.Write(GetRequestRemoteEndPoint(request), "[" + GetSession(request).Username + "] Record was updated for authoritative zone {oldDomain: " + domain + "; domain: " + newDomain + "; type: " + type + "; oldValue: " + value + "; value: " + newValue + "; ttl: " + ttl + "; disabled: " + disable + ";}"); _dnsServer.AuthZoneManager.SaveZoneFile(zoneInfo.Name); } #endregion #region dns apps api string _storeAppsJsonData; DateTime _storeAppsJsonDataUpdatedOn; const int STORE_APPS_JSON_DATA_CACHE_TIME_SECONDS = 300; private async Task GetStoreAppsJsonData() { if ((_storeAppsJsonData == null) || (DateTime.UtcNow > _storeAppsJsonDataUpdatedOn.AddSeconds(STORE_APPS_JSON_DATA_CACHE_TIME_SECONDS))) { SocketsHttpHandler handler = new SocketsHttpHandler(); handler.Proxy = _dnsServer.Proxy; handler.AutomaticDecompression = DecompressionMethods.GZip | DecompressionMethods.Deflate; using (HttpClient http = new HttpClient(handler)) { _storeAppsJsonData = await http.GetStringAsync(_appStoreUri); _storeAppsJsonDataUpdatedOn = DateTime.UtcNow; } } return _storeAppsJsonData; } private async Task ListInstalledAppsAsync(JsonTextWriter jsonWriter) { List apps = new List(_dnsServer.DnsApplicationManager.Applications.Keys); apps.Sort(); dynamic jsonStoreAppsArray = null; if (apps.Count > 0) { try { string storeAppsJsonData = await GetStoreAppsJsonData().WithTimeout(5000); jsonStoreAppsArray = JsonConvert.DeserializeObject(storeAppsJsonData); } catch { } } jsonWriter.WritePropertyName("apps"); jsonWriter.WriteStartArray(); foreach (string app in apps) { if (_dnsServer.DnsApplicationManager.Applications.TryGetValue(app, out DnsApplication application)) { jsonWriter.WriteStartObject(); jsonWriter.WritePropertyName("name"); jsonWriter.WriteValue(application.AppName); jsonWriter.WritePropertyName("version"); jsonWriter.WriteValue(GetCleanVersion(application.Version)); if (jsonStoreAppsArray != null) { foreach (dynamic jsonStoreApp in jsonStoreAppsArray) { string name = jsonStoreApp.name.Value; if (name.Equals(application.AppName)) { string version = jsonStoreApp.version.Value; string url = jsonStoreApp.url.Value; jsonWriter.WritePropertyName("updateVersion"); jsonWriter.WriteValue(version); jsonWriter.WritePropertyName("updateUrl"); jsonWriter.WriteValue(url); jsonWriter.WritePropertyName("updateAvailable"); jsonWriter.WriteValue(new Version(version) > application.Version); break; } } } jsonWriter.WritePropertyName("appRecordRequestHandlers"); { jsonWriter.WriteStartArray(); foreach (KeyValuePair handler in application.DnsAppRecordRequestHandlers) { jsonWriter.WriteStartObject(); jsonWriter.WritePropertyName("classPath"); jsonWriter.WriteValue(handler.Key); jsonWriter.WritePropertyName("description"); jsonWriter.WriteValue(handler.Value.Description); jsonWriter.WritePropertyName("recordDataTemplate"); jsonWriter.WriteValue(handler.Value.ApplicationRecordDataTemplate); jsonWriter.WriteEndObject(); } jsonWriter.WriteEndArray(); } jsonWriter.WritePropertyName("requestControllers"); { jsonWriter.WriteStartArray(); foreach (KeyValuePair controller in application.DnsRequestControllers) { jsonWriter.WriteStartObject(); jsonWriter.WritePropertyName("classPath"); jsonWriter.WriteValue(controller.Key); jsonWriter.WritePropertyName("description"); jsonWriter.WriteValue(controller.Value.Description); jsonWriter.WriteEndObject(); } jsonWriter.WriteEndArray(); } jsonWriter.WritePropertyName("authoritativeRequestHandlers"); { jsonWriter.WriteStartArray(); foreach (KeyValuePair handler in application.DnsAuthoritativeRequestHandlers) { jsonWriter.WriteStartObject(); jsonWriter.WritePropertyName("classPath"); jsonWriter.WriteValue(handler.Key); jsonWriter.WritePropertyName("description"); jsonWriter.WriteValue(handler.Value.Description); jsonWriter.WriteEndObject(); } jsonWriter.WriteEndArray(); } jsonWriter.WritePropertyName("loggers"); { jsonWriter.WriteStartArray(); foreach (KeyValuePair handler in application.DnsLoggers) { jsonWriter.WriteStartObject(); jsonWriter.WritePropertyName("classPath"); jsonWriter.WriteValue(handler.Key); jsonWriter.WritePropertyName("description"); jsonWriter.WriteValue(handler.Value.Description); jsonWriter.WriteEndObject(); } jsonWriter.WriteEndArray(); } jsonWriter.WriteEndObject(); } } jsonWriter.WriteEndArray(); } private async Task ListStoreApps(JsonTextWriter jsonWriter) { string storeAppsJsonData = await GetStoreAppsJsonData(); dynamic jsonStoreAppsArray = JsonConvert.DeserializeObject(storeAppsJsonData); jsonWriter.WritePropertyName("storeApps"); jsonWriter.WriteStartArray(); foreach (dynamic jsonStoreApp in jsonStoreAppsArray) { string name = jsonStoreApp.name.Value; string version = jsonStoreApp.version.Value; string description = jsonStoreApp.description.Value; string url = jsonStoreApp.url.Value; string size = jsonStoreApp.size.Value; jsonWriter.WriteStartObject(); jsonWriter.WritePropertyName("name"); jsonWriter.WriteValue(name); jsonWriter.WritePropertyName("version"); jsonWriter.WriteValue(version); jsonWriter.WritePropertyName("description"); jsonWriter.WriteValue(description); jsonWriter.WritePropertyName("url"); jsonWriter.WriteValue(url); jsonWriter.WritePropertyName("size"); jsonWriter.WriteValue(size); bool installed = _dnsServer.DnsApplicationManager.Applications.TryGetValue(name, out DnsApplication installedApp); jsonWriter.WritePropertyName("installed"); jsonWriter.WriteValue(installed); if (installed) { jsonWriter.WritePropertyName("installedVersion"); jsonWriter.WriteValue(GetCleanVersion(installedApp.Version)); jsonWriter.WritePropertyName("updateAvailable"); jsonWriter.WriteValue(new Version(version) > installedApp.Version); } jsonWriter.WriteEndObject(); } jsonWriter.WriteEndArray(); } private async Task DownloadAndInstallAppAsync(HttpListenerRequest request) { string name = request.QueryString["name"]; if (string.IsNullOrEmpty(name)) throw new DnsWebServiceException("Parameter 'name' missing."); string url = request.QueryString["url"]; if (string.IsNullOrEmpty(url)) throw new DnsWebServiceException("Parameter 'url' missing."); if (!url.StartsWith("https://", StringComparison.OrdinalIgnoreCase)) throw new DnsWebServiceException("Parameter 'url' value must start with 'https://'."); string tmpFile = Path.GetTempFileName(); try { using (FileStream fS = new FileStream(tmpFile, FileMode.Create, FileAccess.ReadWrite)) { //download to temp file SocketsHttpHandler handler = new SocketsHttpHandler(); handler.Proxy = _dnsServer.Proxy; handler.AutomaticDecompression = DecompressionMethods.GZip | DecompressionMethods.Deflate; using (HttpClient http = new HttpClient(handler)) { using (Stream httpStream = await http.GetStreamAsync(url)) { await httpStream.CopyToAsync(fS); } } //install app fS.Position = 0; await _dnsServer.DnsApplicationManager.InstallApplicationAsync(name, fS); _log.Write(GetRequestRemoteEndPoint(request), "[" + GetSession(request).Username + "] DNS application '" + name + "' was installed successfully from: " + url); } } finally { try { File.Delete(tmpFile); } catch (Exception ex) { _log.Write(ex); } } } private async Task DownloadAndUpdateAppAsync(HttpListenerRequest request) { string name = request.QueryString["name"]; if (string.IsNullOrEmpty(name)) throw new DnsWebServiceException("Parameter 'name' missing."); string url = request.QueryString["url"]; if (string.IsNullOrEmpty(url)) throw new DnsWebServiceException("Parameter 'url' missing."); if (!url.StartsWith("https://", StringComparison.OrdinalIgnoreCase)) throw new DnsWebServiceException("Parameter 'url' value must start with 'https://'."); string tmpFile = Path.GetTempFileName(); try { using (FileStream fS = new FileStream(tmpFile, FileMode.Create, FileAccess.ReadWrite)) { //download to temp file SocketsHttpHandler handler = new SocketsHttpHandler(); handler.Proxy = _dnsServer.Proxy; handler.AutomaticDecompression = DecompressionMethods.GZip | DecompressionMethods.Deflate; using (HttpClient http = new HttpClient(handler)) { using (Stream httpStream = await http.GetStreamAsync(url)) { await httpStream.CopyToAsync(fS); } } //update app fS.Position = 0; await _dnsServer.DnsApplicationManager.UpdateApplicationAsync(name, fS); _log.Write(GetRequestRemoteEndPoint(request), "[" + GetSession(request).Username + "] DNS application '" + name + "' was updated successfully from: " + url); } } finally { try { File.Delete(tmpFile); } catch (Exception ex) { _log.Write(ex); } } } private async Task InstallAppAsync(HttpListenerRequest request) { string name = request.QueryString["name"]; if (string.IsNullOrEmpty(name)) throw new DnsWebServiceException("Parameter 'name' missing."); #region skip to content int crlfCount = 0; int byteRead; while (crlfCount != 4) { byteRead = request.InputStream.ReadByte(); switch (byteRead) { case -1: throw new EndOfStreamException(); case 13: //CR case 10: //LF crlfCount++; break; default: crlfCount = 0; break; } } #endregion string tmpFile = Path.GetTempFileName(); try { using (FileStream fS = new FileStream(tmpFile, FileMode.Create, FileAccess.ReadWrite)) { //write to temp file await request.InputStream.CopyToAsync(fS); //install app fS.Position = 0; await _dnsServer.DnsApplicationManager.InstallApplicationAsync(name, fS); _log.Write(GetRequestRemoteEndPoint(request), "[" + GetSession(request).Username + "] DNS application '" + name + "' was installed successfully."); } } finally { try { File.Delete(tmpFile); } catch (Exception ex) { _log.Write(ex); } } } private async Task UpdateAppAsync(HttpListenerRequest request) { string name = request.QueryString["name"]; if (string.IsNullOrEmpty(name)) throw new DnsWebServiceException("Parameter 'name' missing."); #region skip to content int crlfCount = 0; int byteRead; while (crlfCount != 4) { byteRead = request.InputStream.ReadByte(); switch (byteRead) { case -1: throw new EndOfStreamException(); case 13: //CR case 10: //LF crlfCount++; break; default: crlfCount = 0; break; } } #endregion string tmpFile = Path.GetTempFileName(); try { using (FileStream fS = new FileStream(tmpFile, FileMode.Create, FileAccess.ReadWrite)) { //write to temp file await request.InputStream.CopyToAsync(fS); //update app fS.Position = 0; await _dnsServer.DnsApplicationManager.UpdateApplicationAsync(name, fS); _log.Write(GetRequestRemoteEndPoint(request), "[" + GetSession(request).Username + "] DNS application '" + name + "' was updated successfully."); } } finally { try { File.Delete(tmpFile); } catch (Exception ex) { _log.Write(ex); } } } private void UninstallApp(HttpListenerRequest request) { string name = request.QueryString["name"]; if (string.IsNullOrEmpty(name)) throw new DnsWebServiceException("Parameter 'name' missing."); _dnsServer.DnsApplicationManager.UninstallApplication(name); _log.Write(GetRequestRemoteEndPoint(request), "[" + GetSession(request).Username + "] DNS application '" + name + "' was uninstalled successfully."); } private async Task GetAppConfigAsync(HttpListenerRequest request, JsonTextWriter jsonWriter) { string name = request.QueryString["name"]; if (string.IsNullOrEmpty(name)) throw new DnsWebServiceException("Parameter 'name' missing."); if (!_dnsServer.DnsApplicationManager.Applications.TryGetValue(name, out DnsApplication application)) throw new DnsWebServiceException("DNS application was not found: " + name); string config = await application.GetConfigAsync(); jsonWriter.WritePropertyName("config"); jsonWriter.WriteValue(config); } private async Task SetAppConfigAsync(HttpListenerRequest request) { string name = request.QueryString["name"]; if (string.IsNullOrEmpty(name)) throw new DnsWebServiceException("Parameter 'name' missing."); if (!_dnsServer.DnsApplicationManager.Applications.TryGetValue(name, out DnsApplication application)) throw new DnsWebServiceException("DNS application was not found: " + name); string formRequest; using (StreamReader sR = new StreamReader(request.InputStream, request.ContentEncoding)) { formRequest = sR.ReadToEnd(); } string[] formParts = formRequest.Split('&'); foreach (string formPart in formParts) { if (formPart.StartsWith("config=")) { string config = formPart.Substring(7); if (config.Length == 0) config = null; await application.SetConfigAsync(config); break; } } } #endregion #region dns client api private async Task ResolveQuery(HttpListenerRequest request, JsonTextWriter jsonWriter) { string server = request.QueryString["server"]; if (string.IsNullOrEmpty(server)) throw new DnsWebServiceException("Parameter 'server' missing."); string domain = request.QueryString["domain"]; if (string.IsNullOrEmpty(domain)) throw new DnsWebServiceException("Parameter 'domain' missing."); domain = domain.Trim(new char[] { '\t', ' ', '.' }); string strType = request.QueryString["type"]; if (string.IsNullOrEmpty(strType)) throw new DnsWebServiceException("Parameter 'type' missing."); DnsResourceRecordType type = (DnsResourceRecordType)Enum.Parse(typeof(DnsResourceRecordType), strType); string strProtocol = request.QueryString["protocol"]; if (string.IsNullOrEmpty(strProtocol)) strProtocol = "Udp"; bool importRecords = false; string strImport = request.QueryString["import"]; if (!string.IsNullOrEmpty(strImport)) importRecords = bool.Parse(strImport); NetProxy proxy = _dnsServer.Proxy; bool preferIPv6 = _dnsServer.PreferIPv6; bool randomizeName = false; bool qnameMinimization = _dnsServer.QnameMinimization; DnsTransportProtocol protocol = (DnsTransportProtocol)Enum.Parse(typeof(DnsTransportProtocol), strProtocol, true); const int RETRIES = 1; const int TIMEOUT = 10000; DnsDatagram dnsResponse; if (server.Equals("recursive-resolver", StringComparison.OrdinalIgnoreCase)) { if (type == DnsResourceRecordType.AXFR) throw new DnsServerException("Cannot do zone transfer (AXFR) for 'recursive-resolver'."); DnsQuestionRecord question; if ((type == DnsResourceRecordType.PTR) && IPAddress.TryParse(domain, out IPAddress address)) question = new DnsQuestionRecord(address, DnsClass.IN); else question = new DnsQuestionRecord(domain, type, DnsClass.IN); DnsCache dnsCache = new DnsCache(); dnsCache.MinimumRecordTtl = 0; dnsCache.MaximumRecordTtl = 7 * 24 * 60 * 60; dnsResponse = await DnsClient.RecursiveResolveAsync(question, dnsCache, proxy, preferIPv6, randomizeName, qnameMinimization, false, RETRIES, TIMEOUT); } else { if ((type == DnsResourceRecordType.AXFR) && (protocol == DnsTransportProtocol.Udp)) protocol = DnsTransportProtocol.Tcp; NameServerAddress nameServer; if (server.Equals("this-server", StringComparison.OrdinalIgnoreCase)) { switch (protocol) { case DnsTransportProtocol.Udp: nameServer = _dnsServer.ThisServer; break; case DnsTransportProtocol.Tcp: nameServer = _dnsServer.ThisServer.ChangeProtocol(DnsTransportProtocol.Tcp); break; case DnsTransportProtocol.Tls: throw new DnsServerException("Cannot use DNS-over-TLS protocol for 'this-server'. Please use the TLS certificate domain name as the server."); case DnsTransportProtocol.Https: throw new DnsServerException("Cannot use DNS-over-HTTPS protocol for 'this-server'. Please use the TLS certificate domain name with a url as the server."); case DnsTransportProtocol.HttpsJson: throw new DnsServerException("Cannot use DNS-over-HTTPS (JSON) protocol for 'this-server'. Please use the TLS certificate domain name with a url as the server."); default: throw new InvalidOperationException(); } proxy = null; //no proxy required for this server } else { nameServer = new NameServerAddress(server); if (nameServer.Protocol != protocol) nameServer = nameServer.ChangeProtocol(protocol); if (nameServer.IPEndPoint is null) { if (proxy is null) await nameServer.ResolveIPAddressAsync(_dnsServer); } else if (protocol != DnsTransportProtocol.Tls) { try { await nameServer.ResolveDomainNameAsync(_dnsServer); } catch { } } } dnsResponse = await new DnsClient(nameServer) { Proxy = proxy, PreferIPv6 = preferIPv6, RandomizeName = randomizeName, Retries = RETRIES, Timeout = TIMEOUT }.ResolveAsync(domain, type); if (type == DnsResourceRecordType.AXFR) dnsResponse = dnsResponse.Join(); } if (importRecords) { AuthZoneInfo zoneInfo = _dnsServer.AuthZoneManager.GetAuthZoneInfo(domain); if ((zoneInfo == null) || zoneInfo.Name.Equals("", StringComparison.OrdinalIgnoreCase)) { zoneInfo = _dnsServer.AuthZoneManager.CreatePrimaryZone(domain, _dnsServer.ServerDomain, false); if (zoneInfo == null) throw new DnsServerException("Cannot import records: failed to create primary zone."); } else { switch (zoneInfo.Type) { case AuthZoneType.Primary: break; case AuthZoneType.Forwarder: if (type == DnsResourceRecordType.AXFR) throw new DnsServerException("Cannot import records via zone transfer: import zone must be of primary type."); break; default: throw new DnsServerException("Cannot import records: import zone must be of primary or forwarder type."); } } if (type == DnsResourceRecordType.AXFR) { _dnsServer.AuthZoneManager.SyncZoneTransferRecords(zoneInfo.Name, dnsResponse.Answer); } else { List syncRecords = new List(dnsResponse.Answer.Count); foreach (DnsResourceRecord record in dnsResponse.Answer) { if (record.Name.Equals(zoneInfo.Name, StringComparison.OrdinalIgnoreCase) || record.Name.EndsWith("." + zoneInfo.Name, StringComparison.OrdinalIgnoreCase)) { record.RemoveExpiry(); syncRecords.Add(record); } } _dnsServer.AuthZoneManager.LoadRecords(syncRecords); } _log.Write(GetRequestRemoteEndPoint(request), "[" + GetSession(request).Username + "] DNS Client imported record(s) for authoritative zone {server: " + server + "; zone: " + zoneInfo.Name + "; type: " + type + ";}"); _dnsServer.AuthZoneManager.SaveZoneFile(zoneInfo.Name); } jsonWriter.WritePropertyName("result"); jsonWriter.WriteRawValue(JsonConvert.SerializeObject(dnsResponse, new StringEnumConverter())); } #endregion #region logs api private void ListLogs(JsonTextWriter jsonWriter) { string[] logFiles = _log.ListLogFiles(); Array.Sort(logFiles); Array.Reverse(logFiles); jsonWriter.WritePropertyName("logFiles"); jsonWriter.WriteStartArray(); foreach (string logFile in logFiles) { jsonWriter.WriteStartObject(); jsonWriter.WritePropertyName("fileName"); jsonWriter.WriteValue(Path.GetFileNameWithoutExtension(logFile)); jsonWriter.WritePropertyName("size"); jsonWriter.WriteValue(WebUtilities.GetFormattedSize(new FileInfo(logFile).Length)); jsonWriter.WriteEndObject(); } jsonWriter.WriteEndArray(); } private void DeleteLog(HttpListenerRequest request) { string log = request.QueryString["log"]; if (string.IsNullOrEmpty(log)) throw new DnsWebServiceException("Parameter 'log' missing."); _log.DeleteLog(log); _log.Write(GetRequestRemoteEndPoint(request), "[" + GetSession(request).Username + "] Log file was deleted: " + log); } private void DeleteAllLogs(HttpListenerRequest request) { _log.DeleteAllLogs(); _log.Write(GetRequestRemoteEndPoint(request), "[" + GetSession(request).Username + "] All log files were deleted."); } private void DeleteAllStats(HttpListenerRequest request) { _dnsServer.StatsManager.DeleteAllStats(); _log.Write(GetRequestRemoteEndPoint(request), "[" + GetSession(request).Username + "] All stats files were deleted."); } private async Task QueryLogsAsync(HttpListenerRequest request, JsonTextWriter jsonWriter) { string name = request.QueryString["name"]; if (string.IsNullOrEmpty(name)) throw new DnsWebServiceException("Parameter 'name' missing."); string classPath = request.QueryString["classPath"]; if (string.IsNullOrEmpty(classPath)) throw new DnsWebServiceException("Parameter 'classPath' missing."); if (!_dnsServer.DnsApplicationManager.Applications.TryGetValue(name, out DnsApplication application)) throw new DnsWebServiceException("DNS application was not found: " + name); if (!application.DnsLoggers.TryGetValue(classPath, out IDnsLogger logger)) throw new DnsWebServiceException("DNS application '" + classPath + "' class path was not found: " + name); long pageNumber; string strPageNumber = request.QueryString["pageNumber"]; if (string.IsNullOrEmpty(strPageNumber)) pageNumber = 1; else pageNumber = long.Parse(strPageNumber); int entriesPerPage; string strEntriesPerPage = request.QueryString["entriesPerPage"]; if (string.IsNullOrEmpty(strEntriesPerPage)) entriesPerPage = 25; else entriesPerPage = int.Parse(strEntriesPerPage); DateTime? start; string strStart = request.QueryString["start"]; if (string.IsNullOrEmpty(strStart)) start = null; else start = DateTime.ParseExact(strStart, "yyyy-MM-dd HH:mm:ss", CultureInfo.InvariantCulture, DateTimeStyles.AssumeLocal | DateTimeStyles.AdjustToUniversal); DateTime? end; string strEnd = request.QueryString["end"]; if (string.IsNullOrEmpty(strEnd)) end = null; else end = DateTime.ParseExact(strEnd, "yyyy-MM-dd HH:mm:ss", CultureInfo.InvariantCulture, DateTimeStyles.AssumeLocal | DateTimeStyles.AdjustToUniversal); IPAddress clientIpAddress; string strClientIpAddress = request.QueryString["clientIpAddress"]; if (string.IsNullOrEmpty(strClientIpAddress)) clientIpAddress = null; else clientIpAddress = IPAddress.Parse(strClientIpAddress); DnsTransportProtocol? protocol; string strProtocol = request.QueryString["protocol"]; if (string.IsNullOrEmpty(strProtocol)) protocol = null; else protocol = Enum.Parse(strProtocol, true); DnsServerResponseType? responseType; string strResponseType = request.QueryString["responseType"]; if (string.IsNullOrEmpty(strResponseType)) responseType = null; else responseType = Enum.Parse(strResponseType, true); DnsResponseCode? rcode; string strRcode = request.QueryString["rcode"]; if (string.IsNullOrEmpty(strRcode)) rcode = null; else rcode = Enum.Parse(strRcode, true); string qname = request.QueryString["qname"]; if (string.IsNullOrEmpty(qname)) qname = null; DnsResourceRecordType? qtype; string strQtype = request.QueryString["qtype"]; if (string.IsNullOrEmpty(strQtype)) qtype = null; else qtype = Enum.Parse(strQtype, true); DnsClass? qclass; string strQclass = request.QueryString["qclass"]; if (string.IsNullOrEmpty(strQclass)) qclass = null; else qclass = Enum.Parse(strQclass, true); DnsLogPage page = await logger.QueryLogsAsync(pageNumber, entriesPerPage, start, end, clientIpAddress, protocol, responseType, rcode, qname, qtype, qclass); jsonWriter.WritePropertyName("pageNumber"); jsonWriter.WriteValue(page.PageNumber); jsonWriter.WritePropertyName("totalPages"); jsonWriter.WriteValue(page.TotalPages); jsonWriter.WritePropertyName("totalEntries"); jsonWriter.WriteValue(page.TotalEntries); jsonWriter.WritePropertyName("entries"); jsonWriter.WriteStartArray(); foreach (DnsLogEntry entry in page.Entries) { jsonWriter.WriteStartObject(); jsonWriter.WritePropertyName("rowNumber"); jsonWriter.WriteValue(entry.RowNumber); jsonWriter.WritePropertyName("timestamp"); jsonWriter.WriteValue(entry.Timestamp); jsonWriter.WritePropertyName("clientIpAddress"); jsonWriter.WriteValue(entry.ClientIpAddress.ToString()); jsonWriter.WritePropertyName("protocol"); jsonWriter.WriteValue(entry.Protocol.ToString()); jsonWriter.WritePropertyName("responseType"); jsonWriter.WriteValue(entry.ResponseType.ToString()); jsonWriter.WritePropertyName("rcode"); jsonWriter.WriteValue(entry.RCODE.ToString()); jsonWriter.WritePropertyName("qname"); jsonWriter.WriteValue(entry.Question.Name); jsonWriter.WritePropertyName("qtype"); jsonWriter.WriteValue(entry.Question.Type.ToString()); jsonWriter.WritePropertyName("qclass"); jsonWriter.WriteValue(entry.Question.Class.ToString()); jsonWriter.WritePropertyName("answer"); jsonWriter.WriteValue(entry.Answer); jsonWriter.WriteEndObject(); } jsonWriter.WriteEndArray(); } #endregion #region dhcp api private void ListDhcpLeases(JsonTextWriter jsonWriter) { IReadOnlyDictionary scopes = _dhcpServer.Scopes; //sort by name List sortedScopes = new List(scopes.Count); foreach (KeyValuePair entry in scopes) sortedScopes.Add(entry.Value); sortedScopes.Sort(); jsonWriter.WritePropertyName("leases"); jsonWriter.WriteStartArray(); foreach (Scope scope in sortedScopes) { IReadOnlyDictionary leases = scope.Leases; //sort by address List sortedLeases = new List(leases.Count); foreach (KeyValuePair entry in leases) sortedLeases.Add(entry.Value); sortedLeases.Sort(); foreach (Lease lease in sortedLeases) { jsonWriter.WriteStartObject(); jsonWriter.WritePropertyName("scope"); jsonWriter.WriteValue(scope.Name); jsonWriter.WritePropertyName("type"); jsonWriter.WriteValue(lease.Type.ToString()); jsonWriter.WritePropertyName("hardwareAddress"); jsonWriter.WriteValue(BitConverter.ToString(lease.HardwareAddress)); jsonWriter.WritePropertyName("address"); jsonWriter.WriteValue(lease.Address.ToString()); jsonWriter.WritePropertyName("hostName"); jsonWriter.WriteValue(lease.HostName); jsonWriter.WritePropertyName("leaseObtained"); jsonWriter.WriteValue(lease.LeaseObtained); jsonWriter.WritePropertyName("leaseExpires"); jsonWriter.WriteValue(lease.LeaseExpires); jsonWriter.WriteEndObject(); } } jsonWriter.WriteEndArray(); } private void ListDhcpScopes(JsonTextWriter jsonWriter) { IReadOnlyDictionary scopes = _dhcpServer.Scopes; //sort by name List sortedScopes = new List(scopes.Count); foreach (KeyValuePair entry in scopes) sortedScopes.Add(entry.Value); sortedScopes.Sort(); jsonWriter.WritePropertyName("scopes"); jsonWriter.WriteStartArray(); foreach (Scope scope in sortedScopes) { jsonWriter.WriteStartObject(); jsonWriter.WritePropertyName("name"); jsonWriter.WriteValue(scope.Name); jsonWriter.WritePropertyName("enabled"); jsonWriter.WriteValue(scope.Enabled); jsonWriter.WritePropertyName("startingAddress"); jsonWriter.WriteValue(scope.StartingAddress.ToString()); jsonWriter.WritePropertyName("endingAddress"); jsonWriter.WriteValue(scope.EndingAddress.ToString()); jsonWriter.WritePropertyName("subnetMask"); jsonWriter.WriteValue(scope.SubnetMask.ToString()); jsonWriter.WritePropertyName("networkAddress"); jsonWriter.WriteValue(scope.NetworkAddress.ToString()); jsonWriter.WritePropertyName("broadcastAddress"); jsonWriter.WriteValue(scope.BroadcastAddress.ToString()); if (scope.InterfaceAddress != null) { jsonWriter.WritePropertyName("interfaceAddress"); jsonWriter.WriteValue(scope.InterfaceAddress.ToString()); } jsonWriter.WriteEndObject(); } jsonWriter.WriteEndArray(); } private void GetDhcpScope(HttpListenerRequest request, JsonTextWriter jsonWriter) { string scopeName = request.QueryString["name"]; if (string.IsNullOrEmpty(scopeName)) throw new DnsWebServiceException("Parameter 'name' missing."); Scope scope = _dhcpServer.GetScope(scopeName); if (scope == null) throw new DnsWebServiceException("DHCP scope was not found: " + scopeName); jsonWriter.WritePropertyName("name"); jsonWriter.WriteValue(scope.Name); jsonWriter.WritePropertyName("startingAddress"); jsonWriter.WriteValue(scope.StartingAddress.ToString()); jsonWriter.WritePropertyName("endingAddress"); jsonWriter.WriteValue(scope.EndingAddress.ToString()); jsonWriter.WritePropertyName("subnetMask"); jsonWriter.WriteValue(scope.SubnetMask.ToString()); jsonWriter.WritePropertyName("leaseTimeDays"); jsonWriter.WriteValue(scope.LeaseTimeDays); jsonWriter.WritePropertyName("leaseTimeHours"); jsonWriter.WriteValue(scope.LeaseTimeHours); jsonWriter.WritePropertyName("leaseTimeMinutes"); jsonWriter.WriteValue(scope.LeaseTimeMinutes); jsonWriter.WritePropertyName("offerDelayTime"); jsonWriter.WriteValue(scope.OfferDelayTime); if (!string.IsNullOrEmpty(scope.DomainName)) { jsonWriter.WritePropertyName("domainName"); jsonWriter.WriteValue(scope.DomainName); } jsonWriter.WritePropertyName("dnsTtl"); jsonWriter.WriteValue(scope.DnsTtl); if (scope.ServerAddress != null) { jsonWriter.WritePropertyName("serverAddress"); jsonWriter.WriteValue(scope.ServerAddress.ToString()); } if (scope.ServerHostName != null) { jsonWriter.WritePropertyName("serverHostName"); jsonWriter.WriteValue(scope.ServerHostName); } if (scope.BootFileName != null) { jsonWriter.WritePropertyName("bootFileName"); jsonWriter.WriteValue(scope.BootFileName); } if (scope.RouterAddress != null) { jsonWriter.WritePropertyName("routerAddress"); jsonWriter.WriteValue(scope.RouterAddress.ToString()); } jsonWriter.WritePropertyName("useThisDnsServer"); jsonWriter.WriteValue(scope.UseThisDnsServer); if (scope.DnsServers != null) { jsonWriter.WritePropertyName("dnsServers"); jsonWriter.WriteStartArray(); foreach (IPAddress dnsServer in scope.DnsServers) jsonWriter.WriteValue(dnsServer.ToString()); jsonWriter.WriteEndArray(); } if (scope.WinsServers != null) { jsonWriter.WritePropertyName("winsServers"); jsonWriter.WriteStartArray(); foreach (IPAddress winsServer in scope.WinsServers) jsonWriter.WriteValue(winsServer.ToString()); jsonWriter.WriteEndArray(); } if (scope.NtpServers != null) { jsonWriter.WritePropertyName("ntpServers"); jsonWriter.WriteStartArray(); foreach (IPAddress ntpServer in scope.NtpServers) jsonWriter.WriteValue(ntpServer.ToString()); jsonWriter.WriteEndArray(); } if (scope.StaticRoutes != null) { jsonWriter.WritePropertyName("staticRoutes"); jsonWriter.WriteStartArray(); foreach (ClasslessStaticRouteOption.Route route in scope.StaticRoutes) { jsonWriter.WriteStartObject(); jsonWriter.WritePropertyName("destination"); jsonWriter.WriteValue(route.Destination.ToString()); jsonWriter.WritePropertyName("subnetMask"); jsonWriter.WriteValue(route.SubnetMask.ToString()); jsonWriter.WritePropertyName("router"); jsonWriter.WriteValue(route.Router.ToString()); jsonWriter.WriteEndObject(); } jsonWriter.WriteEndArray(); } if (scope.VendorInfo != null) { jsonWriter.WritePropertyName("vendorInfo"); jsonWriter.WriteStartArray(); foreach (KeyValuePair entry in scope.VendorInfo) { jsonWriter.WriteStartObject(); jsonWriter.WritePropertyName("identifier"); jsonWriter.WriteValue(entry.Key); jsonWriter.WritePropertyName("information"); jsonWriter.WriteValue(entry.Value.ToString()); jsonWriter.WriteEndObject(); } jsonWriter.WriteEndArray(); } if (scope.Exclusions != null) { jsonWriter.WritePropertyName("exclusions"); jsonWriter.WriteStartArray(); foreach (Exclusion exclusion in scope.Exclusions) { jsonWriter.WriteStartObject(); jsonWriter.WritePropertyName("startingAddress"); jsonWriter.WriteValue(exclusion.StartingAddress.ToString()); jsonWriter.WritePropertyName("endingAddress"); jsonWriter.WriteValue(exclusion.EndingAddress.ToString()); jsonWriter.WriteEndObject(); } jsonWriter.WriteEndArray(); } jsonWriter.WritePropertyName("reservedLeases"); jsonWriter.WriteStartArray(); foreach (Lease reservedLease in scope.ReservedLeases) { jsonWriter.WriteStartObject(); jsonWriter.WritePropertyName("hostName"); jsonWriter.WriteValue(reservedLease.HostName); jsonWriter.WritePropertyName("hardwareAddress"); jsonWriter.WriteValue(BitConverter.ToString(reservedLease.HardwareAddress)); jsonWriter.WritePropertyName("address"); jsonWriter.WriteValue(reservedLease.Address.ToString()); jsonWriter.WritePropertyName("comments"); jsonWriter.WriteValue(reservedLease.Comments); jsonWriter.WriteEndObject(); } jsonWriter.WriteEndArray(); jsonWriter.WritePropertyName("allowOnlyReservedLeases"); jsonWriter.WriteValue(scope.AllowOnlyReservedLeases); } private async Task SetDhcpScopeAsync(HttpListenerRequest request) { string scopeName = request.QueryString["name"]; if (string.IsNullOrEmpty(scopeName)) throw new DnsWebServiceException("Parameter 'name' missing."); string newName = request.QueryString["newName"]; if (!string.IsNullOrEmpty(newName) && !newName.Equals(scopeName)) { _dhcpServer.RenameScope(scopeName, newName); _log.Write(GetRequestRemoteEndPoint(request), "[" + GetSession(request).Username + "] DHCP scope was renamed successfully: '" + scopeName + "' to '" + newName + "'"); scopeName = newName; } string strStartingAddress = request.QueryString["startingAddress"]; if (string.IsNullOrEmpty(strStartingAddress)) throw new DnsWebServiceException("Parameter 'startingAddress' missing."); string strEndingAddress = request.QueryString["endingAddress"]; if (string.IsNullOrEmpty(strEndingAddress)) throw new DnsWebServiceException("Parameter 'endingAddress' missing."); string strSubnetMask = request.QueryString["subnetMask"]; if (string.IsNullOrEmpty(strSubnetMask)) throw new DnsWebServiceException("Parameter 'subnetMask' missing."); bool scopeExists; Scope scope = _dhcpServer.GetScope(scopeName); if (scope == null) { //scope does not exists; create new scope scopeExists = false; scope = new Scope(scopeName, true, IPAddress.Parse(strStartingAddress), IPAddress.Parse(strEndingAddress), IPAddress.Parse(strSubnetMask)); } else { scopeExists = true; IPAddress startingAddress = IPAddress.Parse(strStartingAddress); IPAddress endingAddress = IPAddress.Parse(strEndingAddress); //validate scope address foreach (KeyValuePair entry in _dhcpServer.Scopes) { Scope existingScope = entry.Value; if (existingScope.Equals(scope)) continue; if (existingScope.IsAddressInRange(startingAddress) || existingScope.IsAddressInRange(endingAddress)) throw new DhcpServerException("Scope with overlapping range already exists: " + existingScope.StartingAddress.ToString() + "-" + existingScope.EndingAddress.ToString()); } scope.ChangeNetwork(startingAddress, endingAddress, IPAddress.Parse(strSubnetMask)); } string strLeaseTimeDays = request.QueryString["leaseTimeDays"]; if (!string.IsNullOrEmpty(strLeaseTimeDays)) scope.LeaseTimeDays = ushort.Parse(strLeaseTimeDays); string strLeaseTimeHours = request.QueryString["leaseTimeHours"]; if (!string.IsNullOrEmpty(strLeaseTimeHours)) scope.LeaseTimeHours = byte.Parse(strLeaseTimeHours); string strLeaseTimeMinutes = request.QueryString["leaseTimeMinutes"]; if (!string.IsNullOrEmpty(strLeaseTimeMinutes)) scope.LeaseTimeMinutes = byte.Parse(strLeaseTimeMinutes); string strOfferDelayTime = request.QueryString["offerDelayTime"]; if (!string.IsNullOrEmpty(strOfferDelayTime)) scope.OfferDelayTime = ushort.Parse(strOfferDelayTime); string strDomainName = request.QueryString["domainName"]; if (strDomainName != null) scope.DomainName = strDomainName.Length == 0 ? null : strDomainName; string strDnsTtl = request.QueryString["dnsTtl"]; if (!string.IsNullOrEmpty(strDnsTtl)) scope.DnsTtl = uint.Parse(strDnsTtl); string strServerAddress = request.QueryString["serverAddress"]; if (strServerAddress != null) scope.ServerAddress = strServerAddress.Length == 0 ? null : IPAddress.Parse(strServerAddress); string strServerHostName = request.QueryString["serverHostName"]; if (strServerHostName != null) scope.ServerHostName = strServerHostName.Length == 0 ? null : strServerHostName; string strBootFileName = request.QueryString["bootFileName"]; if (strBootFileName != null) scope.BootFileName = strBootFileName.Length == 0 ? null : strBootFileName; string strRouterAddress = request.QueryString["routerAddress"]; if (strRouterAddress != null) scope.RouterAddress = strRouterAddress.Length == 0 ? null : IPAddress.Parse(strRouterAddress); string strUseThisDnsServer = request.QueryString["useThisDnsServer"]; if (!string.IsNullOrEmpty(strUseThisDnsServer)) scope.UseThisDnsServer = bool.Parse(strUseThisDnsServer); if (!scope.UseThisDnsServer) { string strDnsServers = request.QueryString["dnsServers"]; if (strDnsServers != null) { if (strDnsServers.Length == 0) { scope.DnsServers = null; } else { string[] strDnsServerParts = strDnsServers.Split(new char[] { ',' }, StringSplitOptions.RemoveEmptyEntries); IPAddress[] dnsServers = new IPAddress[strDnsServerParts.Length]; for (int i = 0; i < strDnsServerParts.Length; i++) dnsServers[i] = IPAddress.Parse(strDnsServerParts[i]); scope.DnsServers = dnsServers; } } } string strWinsServers = request.QueryString["winsServers"]; if (strWinsServers != null) { if (strWinsServers.Length == 0) { scope.WinsServers = null; } else { string[] strWinsServerParts = strWinsServers.Split(new char[] { ',' }, StringSplitOptions.RemoveEmptyEntries); IPAddress[] winsServers = new IPAddress[strWinsServerParts.Length]; for (int i = 0; i < strWinsServerParts.Length; i++) winsServers[i] = IPAddress.Parse(strWinsServerParts[i]); scope.WinsServers = winsServers; } } string strNtpServers = request.QueryString["ntpServers"]; if (strNtpServers != null) { if (strNtpServers.Length == 0) { scope.NtpServers = null; } else { string[] strNtpServerParts = strNtpServers.Split(new char[] { ',' }, StringSplitOptions.RemoveEmptyEntries); IPAddress[] ntpServers = new IPAddress[strNtpServerParts.Length]; for (int i = 0; i < strNtpServerParts.Length; i++) ntpServers[i] = IPAddress.Parse(strNtpServerParts[i]); scope.NtpServers = ntpServers; } } string strStaticRoutes = request.QueryString["staticRoutes"]; if (strStaticRoutes != null) { if (strStaticRoutes.Length == 0) { scope.StaticRoutes = null; } else { string[] strStaticRoutesParts = strStaticRoutes.Split('|'); List staticRoutes = new List(); for (int i = 0; i < strStaticRoutesParts.Length; i += 3) { staticRoutes.Add(new ClasslessStaticRouteOption.Route(IPAddress.Parse(strStaticRoutesParts[i + 0]), IPAddress.Parse(strStaticRoutesParts[i + 1]), IPAddress.Parse(strStaticRoutesParts[i + 2]))); } scope.StaticRoutes = staticRoutes; } } string strVendorInfo = request.QueryString["vendorInfo"]; if (strVendorInfo != null) { if (strVendorInfo.Length == 0) { scope.VendorInfo = null; } else { string[] strVendorInfoParts = strVendorInfo.Split('|'); Dictionary vendorInfo = new Dictionary(); for (int i = 0; i < strVendorInfoParts.Length; i += 2) { vendorInfo.Add(strVendorInfoParts[i + 0], new VendorSpecificInformationOption(strVendorInfoParts[i + 1])); } scope.VendorInfo = vendorInfo; } } string strExclusions = request.QueryString["exclusions"]; if (strExclusions != null) { if (strExclusions.Length == 0) { scope.Exclusions = null; } else { string[] strExclusionsParts = strExclusions.Split('|'); List exclusions = new List(); for (int i = 0; i < strExclusionsParts.Length; i += 2) { exclusions.Add(new Exclusion(IPAddress.Parse(strExclusionsParts[i + 0]), IPAddress.Parse(strExclusionsParts[i + 1]))); } scope.Exclusions = exclusions; } } string strReservedLeases = request.QueryString["reservedLeases"]; if (strReservedLeases != null) { if (strReservedLeases.Length == 0) { scope.ReservedLeases = null; } else { string[] strReservedLeaseParts = strReservedLeases.Split('|'); List reservedLeases = new List(); for (int i = 0; i < strReservedLeaseParts.Length; i += 4) { reservedLeases.Add(new Lease(LeaseType.Reserved, strReservedLeaseParts[i + 0], DhcpMessageHardwareAddressType.Ethernet, strReservedLeaseParts[i + 1], IPAddress.Parse(strReservedLeaseParts[i + 2]), strReservedLeaseParts[i + 3])); } scope.ReservedLeases = reservedLeases; } } string strAllowOnlyReservedLeases = request.QueryString["allowOnlyReservedLeases"]; if (!string.IsNullOrEmpty(strAllowOnlyReservedLeases)) scope.AllowOnlyReservedLeases = bool.Parse(strAllowOnlyReservedLeases); if (scopeExists) { _dhcpServer.SaveScope(scopeName); _log.Write(GetRequestRemoteEndPoint(request), "[" + GetSession(request).Username + "] DHCP scope was updated successfully: " + scopeName); } else { await _dhcpServer.AddScopeAsync(scope); _log.Write(GetRequestRemoteEndPoint(request), "[" + GetSession(request).Username + "] DHCP scope was added successfully: " + scopeName); } } private async Task EnableDhcpScopeAsync(HttpListenerRequest request) { string scopeName = request.QueryString["name"]; if (string.IsNullOrEmpty(scopeName)) throw new DnsWebServiceException("Parameter 'name' missing."); if (!await _dhcpServer.EnableScopeAsync(scopeName)) throw new DnsWebServiceException("Failed to enable DHCP scope, please check logs for details: " + scopeName); _log.Write(GetRequestRemoteEndPoint(request), "[" + GetSession(request).Username + "] DHCP scope was enabled successfully: " + scopeName); } private void DisableDhcpScope(HttpListenerRequest request) { string scopeName = request.QueryString["name"]; if (string.IsNullOrEmpty(scopeName)) throw new DnsWebServiceException("Parameter 'name' missing."); if (!_dhcpServer.DisableScope(scopeName)) throw new DnsWebServiceException("Failed to disable DHCP scope, please check logs for details: " + scopeName); _log.Write(GetRequestRemoteEndPoint(request), "[" + GetSession(request).Username + "] DHCP scope was disabled successfully: " + scopeName); } private void DeleteDhcpScope(HttpListenerRequest request) { string scopeName = request.QueryString["name"]; if (string.IsNullOrEmpty(scopeName)) throw new DnsWebServiceException("Parameter 'name' missing."); _dhcpServer.DeleteScope(scopeName); _log.Write(GetRequestRemoteEndPoint(request), "[" + GetSession(request).Username + "] DHCP scope was deleted successfully: " + scopeName); } private void ConvertToReservedLease(HttpListenerRequest request) { string scopeName = request.QueryString["name"]; if (string.IsNullOrEmpty(scopeName)) throw new DnsWebServiceException("Parameter 'name' missing."); Scope scope = _dhcpServer.GetScope(scopeName); if (scope == null) throw new DnsWebServiceException("DHCP scope does not exists: " + scopeName); string strHardwareAddress = request.QueryString["hardwareAddress"]; if (string.IsNullOrEmpty(strHardwareAddress)) throw new DnsWebServiceException("Parameter 'hardwareAddress' missing."); scope.ConvertToReservedLease(strHardwareAddress); _dhcpServer.SaveScope(scopeName); _log.Write(GetRequestRemoteEndPoint(request), "[" + GetSession(request).Username + "] DHCP scope was updated successfully: " + scopeName); } private void ConvertToDynamicLease(HttpListenerRequest request) { string scopeName = request.QueryString["name"]; if (string.IsNullOrEmpty(scopeName)) throw new DnsWebServiceException("Parameter 'name' missing."); Scope scope = _dhcpServer.GetScope(scopeName); if (scope == null) throw new DnsWebServiceException("DHCP scope does not exists: " + scopeName); string strHardwareAddress = request.QueryString["hardwareAddress"]; if (string.IsNullOrEmpty(strHardwareAddress)) throw new DnsWebServiceException("Parameter 'hardwareAddress' missing."); scope.ConvertToDynamicLease(strHardwareAddress); _dhcpServer.SaveScope(scopeName); _log.Write(GetRequestRemoteEndPoint(request), "[" + GetSession(request).Username + "] DHCP scope was updated successfully: " + scopeName); } #endregion #region auth private void SetCredentials(string username, string password) { username = username.ToLower(); string passwordHash = GetPasswordHash(username, password); _credentials[username] = passwordHash; } private void LoadCredentials(string username, string passwordHash) { username = username.ToLower(); _credentials[username] = passwordHash; } private static string GetPasswordHash(string username, string password) { using (HMAC hmac = new HMACSHA256(Encoding.UTF8.GetBytes(password))) { return BitConverter.ToString(hmac.ComputeHash(Encoding.UTF8.GetBytes(username))).Replace("-", "").ToLower(); } } #endregion #region block list private bool ForceUpdateBlockLists() { if ((_dnsServer.BlockListZoneManager.AllowListUrls.Count + _dnsServer.BlockListZoneManager.BlockListUrls.Count) > 0) { _blockListLastUpdatedOn = new DateTime(); StopBlockListUpdateTimer(); StartBlockListUpdateTimer(); return true; } return false; } private void StartBlockListUpdateTimer() { if (_blockListUpdateTimer == null) { _blockListUpdateTimer = new Timer(async delegate (object state) { try { if (DateTime.UtcNow > _blockListLastUpdatedOn.AddHours(_blockListUpdateIntervalHours)) { if (await _dnsServer.BlockListZoneManager.UpdateBlockListsAsync()) { //block lists were updated //save last updated on time _blockListLastUpdatedOn = DateTime.UtcNow; SaveConfigFile(); } } } catch (Exception ex) { _log.Write("DNS Server encountered an error while updating block lists.\r\n" + ex.ToString()); } }, null, BLOCK_LIST_UPDATE_TIMER_INITIAL_INTERVAL, BLOCK_LIST_UPDATE_TIMER_INTERVAL); } } private void StopBlockListUpdateTimer() { if (_blockListUpdateTimer != null) { _blockListUpdateTimer.Dispose(); _blockListUpdateTimer = null; } } #endregion #region tls private void StartTlsCertificateUpdateTimer() { if (_tlsCertificateUpdateTimer == null) { _tlsCertificateUpdateTimer = new Timer(delegate (object state) { if (!string.IsNullOrEmpty(_webServiceTlsCertificatePath)) { try { FileInfo fileInfo = new FileInfo(_webServiceTlsCertificatePath); if (fileInfo.Exists && (fileInfo.LastWriteTimeUtc != _webServiceTlsCertificateLastModifiedOn)) LoadWebServiceTlsCertificate(_webServiceTlsCertificatePath, _webServiceTlsCertificatePassword); } catch (Exception ex) { _log.Write("DNS Server encountered an error while updating Web Service TLS Certificate: " + _webServiceTlsCertificatePath + "\r\n" + ex.ToString()); } } if (!string.IsNullOrEmpty(_dnsTlsCertificatePath)) { try { FileInfo fileInfo = new FileInfo(_dnsTlsCertificatePath); if (fileInfo.Exists && (fileInfo.LastWriteTimeUtc != _dnsTlsCertificateLastModifiedOn)) LoadDnsTlsCertificate(_dnsTlsCertificatePath, _dnsTlsCertificatePassword); } catch (Exception ex) { _log.Write("DNS Server encountered an error while updating DNS Server TLS Certificate: " + _dnsTlsCertificatePath + "\r\n" + ex.ToString()); } } }, null, TLS_CERTIFICATE_UPDATE_TIMER_INITIAL_INTERVAL, TLS_CERTIFICATE_UPDATE_TIMER_INTERVAL); } } private void StopTlsCertificateUpdateTimer() { if (_tlsCertificateUpdateTimer != null) { _tlsCertificateUpdateTimer.Dispose(); _tlsCertificateUpdateTimer = null; } } private void LoadWebServiceTlsCertificate(string tlsCertificatePath, string tlsCertificatePassword) { FileInfo fileInfo = new FileInfo(tlsCertificatePath); if (!fileInfo.Exists) throw new ArgumentException("Web Service TLS certificate file does not exists: " + tlsCertificatePath); if (Path.GetExtension(tlsCertificatePath) != ".pfx") throw new ArgumentException("Web Service TLS certificate file must be PKCS #12 formatted with .pfx extension: " + tlsCertificatePath); X509Certificate2 certificate = new X509Certificate2(tlsCertificatePath, tlsCertificatePassword); _webServiceTlsCertificate = certificate; _webServiceTlsCertificateLastModifiedOn = fileInfo.LastWriteTimeUtc; _log.Write("Web Service TLS certificate was loaded: " + tlsCertificatePath); } private void LoadDnsTlsCertificate(string tlsCertificatePath, string tlsCertificatePassword) { FileInfo fileInfo = new FileInfo(tlsCertificatePath); if (!fileInfo.Exists) throw new ArgumentException("DNS Server TLS certificate file does not exists: " + tlsCertificatePath); if (Path.GetExtension(tlsCertificatePath) != ".pfx") throw new ArgumentException("DNS Server TLS certificate file must be PKCS #12 formatted with .pfx extension: " + tlsCertificatePath); X509Certificate2 certificate = new X509Certificate2(tlsCertificatePath, tlsCertificatePassword); _dnsServer.Certificate = certificate; _dnsTlsCertificateLastModifiedOn = fileInfo.LastWriteTimeUtc; _log.Write("DNS Server TLS certificate was loaded: " + tlsCertificatePath); } #endregion #region config private void LoadConfigFile() { string configFile = Path.Combine(_configFolder, "dns.config"); try { bool passwordResetOption = false; if (!File.Exists(configFile)) { string passwordResetConfigFile = Path.Combine(_configFolder, "reset.config"); if (File.Exists(passwordResetConfigFile)) { passwordResetOption = true; configFile = passwordResetConfigFile; } } byte version; using (FileStream fS = new FileStream(configFile, FileMode.Open, FileAccess.Read)) { BinaryReader bR = new BinaryReader(fS); if (Encoding.ASCII.GetString(bR.ReadBytes(2)) != "DS") //format throw new InvalidDataException("DnsServer config file format is invalid."); version = bR.ReadByte(); switch (version) { case 2: case 3: case 4: case 5: case 6: case 7: case 8: case 9: case 10: case 11: case 12: case 13: case 14: case 15: case 16: case 17: case 18: case 19: case 20: case 21: case 22: _dnsServer.ServerDomain = bR.ReadShortString(); _webServiceHttpPort = bR.ReadInt32(); if (version >= 13) { { int count = bR.ReadByte(); if (count > 0) { IPAddress[] localAddresses = new IPAddress[count]; for (int i = 0; i < count; i++) localAddresses[i] = IPAddressExtension.Parse(bR); _webServiceLocalAddresses = localAddresses; } } _webServiceTlsPort = bR.ReadInt32(); _webServiceEnableTls = bR.ReadBoolean(); _webServiceHttpToTlsRedirect = bR.ReadBoolean(); _webServiceTlsCertificatePath = bR.ReadShortString(); _webServiceTlsCertificatePassword = bR.ReadShortString(); if (_webServiceTlsCertificatePath.Length == 0) _webServiceTlsCertificatePath = null; if (_webServiceTlsCertificatePath != null) { try { LoadWebServiceTlsCertificate(_webServiceTlsCertificatePath, _webServiceTlsCertificatePassword); } catch (Exception ex) { _log.Write("DNS Server encountered an error while loading Web Service TLS certificate: " + _webServiceTlsCertificatePath + "\r\n" + ex.ToString()); } StartTlsCertificateUpdateTimer(); } } else { _webServiceLocalAddresses = new IPAddress[] { IPAddress.Any, IPAddress.IPv6Any }; _webServiceTlsPort = 53443; _webServiceEnableTls = false; _webServiceHttpToTlsRedirect = false; _webServiceTlsCertificatePath = string.Empty; _webServiceTlsCertificatePassword = string.Empty; } _dnsServer.PreferIPv6 = bR.ReadBoolean(); if (bR.ReadBoolean()) //logQueries _dnsServer.QueryLogManager = _log; if (version >= 14) _dnsServer.StatsManager.MaxStatFileDays = bR.ReadInt32(); else _dnsServer.StatsManager.MaxStatFileDays = 0; if (version >= 17) { _dnsServer.Recursion = (DnsServerRecursion)bR.ReadByte(); { int count = bR.ReadByte(); if (count > 0) { NetworkAddress[] networks = new NetworkAddress[count]; for (int i = 0; i < count; i++) networks[i] = NetworkAddress.Parse(bR); _dnsServer.RecursionDeniedNetworks = networks; } } { int count = bR.ReadByte(); if (count > 0) { NetworkAddress[] networks = new NetworkAddress[count]; for (int i = 0; i < count; i++) networks[i] = NetworkAddress.Parse(bR); _dnsServer.RecursionAllowedNetworks = networks; } } } else { bool allowRecursion = bR.ReadBoolean(); bool allowRecursionOnlyForPrivateNetworks; if (version >= 4) allowRecursionOnlyForPrivateNetworks = bR.ReadBoolean(); else allowRecursionOnlyForPrivateNetworks = true; //default true for security reasons if (allowRecursion) { if (allowRecursionOnlyForPrivateNetworks) _dnsServer.Recursion = DnsServerRecursion.AllowOnlyForPrivateNetworks; else _dnsServer.Recursion = DnsServerRecursion.Allow; } else { _dnsServer.Recursion = DnsServerRecursion.Deny; } } if (version >= 12) _dnsServer.RandomizeName = bR.ReadBoolean(); else _dnsServer.RandomizeName = true; //default true to enable security feature if (version >= 15) _dnsServer.QnameMinimization = bR.ReadBoolean(); else _dnsServer.QnameMinimization = true; //default true to enable privacy feature if (version >= 20) { _dnsServer.QpmLimitRequests = bR.ReadInt32(); _dnsServer.QpmLimitErrors = bR.ReadInt32(); _dnsServer.QpmLimitSampleMinutes = bR.ReadInt32(); _dnsServer.QpmLimitIPv4PrefixLength = bR.ReadInt32(); _dnsServer.QpmLimitIPv6PrefixLength = bR.ReadInt32(); } else if (version >= 17) { _dnsServer.QpmLimitRequests = bR.ReadInt32(); _dnsServer.QpmLimitSampleMinutes = bR.ReadInt32(); _ = bR.ReadInt32(); //read obsolete value _dnsServer.QpmLimitSamplingIntervalInMinutes } else { _dnsServer.QpmLimitRequests = 0; _dnsServer.QpmLimitErrors = 0; _dnsServer.QpmLimitSampleMinutes = 1; _dnsServer.QpmLimitIPv4PrefixLength = 24; _dnsServer.QpmLimitIPv6PrefixLength = 56; } if (version >= 13) { _dnsServer.ServeStale = bR.ReadBoolean(); _dnsServer.CacheZoneManager.ServeStaleTtl = bR.ReadUInt32(); } else { _dnsServer.ServeStale = true; _dnsServer.CacheZoneManager.ServeStaleTtl = CacheZoneManager.SERVE_STALE_TTL; } if (version >= 9) { _dnsServer.CachePrefetchEligibility = bR.ReadInt32(); _dnsServer.CachePrefetchTrigger = bR.ReadInt32(); _dnsServer.CachePrefetchSampleIntervalInMinutes = bR.ReadInt32(); _dnsServer.CachePrefetchSampleEligibilityHitsPerHour = bR.ReadInt32(); } else { _dnsServer.CachePrefetchEligibility = 2; _dnsServer.CachePrefetchTrigger = 9; _dnsServer.CachePrefetchSampleIntervalInMinutes = 5; _dnsServer.CachePrefetchSampleEligibilityHitsPerHour = 30; } NetProxyType proxyType = (NetProxyType)bR.ReadByte(); if (proxyType != NetProxyType.None) { string address = bR.ReadShortString(); int port = bR.ReadInt32(); NetworkCredential credential = null; if (bR.ReadBoolean()) //credential set credential = new NetworkCredential(bR.ReadShortString(), bR.ReadShortString()); _dnsServer.Proxy = NetProxy.CreateProxy(proxyType, address, port, credential); if (version >= 10) { int count = bR.ReadByte(); List bypassList = new List(count); for (int i = 0; i < count; i++) bypassList.Add(new NetProxyBypassItem(bR.ReadShortString())); _dnsServer.Proxy.BypassList = bypassList; } else { _dnsServer.Proxy.BypassList = null; } } else { _dnsServer.Proxy = null; } { int count = bR.ReadByte(); if (count > 0) { NameServerAddress[] forwarders = new NameServerAddress[count]; for (int i = 0; i < count; i++) forwarders[i] = new NameServerAddress(bR); _dnsServer.Forwarders = forwarders; } } if (version <= 10) { DnsTransportProtocol forwarderProtocol = (DnsTransportProtocol)bR.ReadByte(); if (_dnsServer.Forwarders != null) { List forwarders = new List(); foreach (NameServerAddress forwarder in _dnsServer.Forwarders) { if (forwarder.Protocol == forwarderProtocol) forwarders.Add(forwarder); else forwarders.Add(forwarder.ChangeProtocol(forwarderProtocol)); } _dnsServer.Forwarders = forwarders; } } { int count = bR.ReadByte(); if (count > 0) { if (version > 2) { for (int i = 0; i < count; i++) LoadCredentials(bR.ReadShortString(), bR.ReadShortString()); } else { for (int i = 0; i < count; i++) SetCredentials(bR.ReadShortString(), bR.ReadShortString()); } } } if (version <= 6) { int count = bR.ReadInt32(); _configDisabledZones = new List(count); for (int i = 0; i < count; i++) { string domain = bR.ReadShortString(); _configDisabledZones.Add(domain); } } if (version >= 18) _dnsServer.EnableBlocking = bR.ReadBoolean(); else _dnsServer.EnableBlocking = true; if (version >= 18) _dnsServer.BlockingType = (DnsServerBlockingType)bR.ReadByte(); else if (version >= 16) _dnsServer.BlockingType = bR.ReadBoolean() ? DnsServerBlockingType.NxDomain : DnsServerBlockingType.AnyAddress; else _dnsServer.BlockingType = DnsServerBlockingType.AnyAddress; if (version >= 18) { //read custom blocking addresses int count = bR.ReadByte(); if (count > 0) { List dnsARecords = new List(); List dnsAAAARecords = new List(); for (int i = 0; i < count; i++) { IPAddress customAddress = IPAddressExtension.Parse(bR); switch (customAddress.AddressFamily) { case AddressFamily.InterNetwork: dnsARecords.Add(new DnsARecord(customAddress)); break; case AddressFamily.InterNetworkV6: dnsAAAARecords.Add(new DnsAAAARecord(customAddress)); break; } } _dnsServer.CustomBlockingARecords = dnsARecords; _dnsServer.CustomBlockingAAAARecords = dnsAAAARecords; } } else { _dnsServer.CustomBlockingARecords = null; _dnsServer.CustomBlockingAAAARecords = null; } if (version > 4) { //read block list urls int count = bR.ReadByte(); for (int i = 0; i < count; i++) { string listUrl = bR.ReadShortString(); if (listUrl.StartsWith("!")) _dnsServer.BlockListZoneManager.AllowListUrls.Add(new Uri(listUrl.Substring(1))); else _dnsServer.BlockListZoneManager.BlockListUrls.Add(new Uri(listUrl)); } _blockListLastUpdatedOn = bR.ReadDateTime(); if (version >= 13) _blockListUpdateIntervalHours = bR.ReadInt32(); } else { _dnsServer.BlockListZoneManager.AllowListUrls.Clear(); _dnsServer.BlockListZoneManager.BlockListUrls.Clear(); _blockListLastUpdatedOn = DateTime.MinValue; _blockListUpdateIntervalHours = 24; } if (version >= 11) { int count = bR.ReadByte(); if (count > 0) { IPEndPoint[] localEndPoints = new IPEndPoint[count]; for (int i = 0; i < count; i++) localEndPoints[i] = (IPEndPoint)EndPointExtension.Parse(bR); _dnsServer.LocalEndPoints = localEndPoints; } } else if (version >= 6) { int count = bR.ReadByte(); if (count > 0) { IPEndPoint[] localEndPoints = new IPEndPoint[count]; for (int i = 0; i < count; i++) localEndPoints[i] = new IPEndPoint(IPAddressExtension.Parse(bR), 53); _dnsServer.LocalEndPoints = localEndPoints; } } else { _dnsServer.LocalEndPoints = new IPEndPoint[] { new IPEndPoint(IPAddress.Any, 53), new IPEndPoint(IPAddress.IPv6Any, 53) }; } if (version >= 8) { _dnsServer.EnableDnsOverHttp = bR.ReadBoolean(); _dnsServer.EnableDnsOverTls = bR.ReadBoolean(); _dnsServer.EnableDnsOverHttps = bR.ReadBoolean(); _dnsTlsCertificatePath = bR.ReadShortString(); _dnsTlsCertificatePassword = bR.ReadShortString(); if (_dnsTlsCertificatePath.Length == 0) _dnsTlsCertificatePath = null; if (_dnsTlsCertificatePath != null) { try { LoadDnsTlsCertificate(_dnsTlsCertificatePath, _dnsTlsCertificatePassword); } catch (Exception ex) { _log.Write("DNS Server encountered an error while loading DNS Server TLS certificate: " + _dnsTlsCertificatePath + "\r\n" + ex.ToString()); } StartTlsCertificateUpdateTimer(); } } else { _dnsServer.EnableDnsOverHttp = false; _dnsServer.EnableDnsOverTls = false; _dnsServer.EnableDnsOverHttps = false; _dnsTlsCertificatePath = string.Empty; _dnsTlsCertificatePassword = string.Empty; } if (version >= 19) { _dnsServer.CacheZoneManager.MinimumRecordTtl = bR.ReadUInt32(); _dnsServer.CacheZoneManager.MaximumRecordTtl = bR.ReadUInt32(); _dnsServer.CacheZoneManager.NegativeRecordTtl = bR.ReadUInt32(); _dnsServer.CacheZoneManager.FailureRecordTtl = bR.ReadUInt32(); } else { _dnsServer.CacheZoneManager.MinimumRecordTtl = CacheZoneManager.MINIMUM_RECORD_TTL; _dnsServer.CacheZoneManager.MaximumRecordTtl = CacheZoneManager.MAXIMUM_RECORD_TTL; _dnsServer.CacheZoneManager.NegativeRecordTtl = CacheZoneManager.NEGATIVE_RECORD_TTL; _dnsServer.CacheZoneManager.FailureRecordTtl = CacheZoneManager.FAILURE_RECORD_TTL; } if (version >= 21) { int count = bR.ReadByte(); Dictionary tsigKeys = new Dictionary(count); for (int i = 0; i < count; i++) { string keyName = bR.ReadShortString(); string sharedSecret = bR.ReadShortString(); TsigAlgorithm algorithm = (TsigAlgorithm)bR.ReadByte(); tsigKeys.Add(keyName, new TsigKey(keyName, sharedSecret, algorithm)); } _dnsServer.TsigKeys = tsigKeys; } else if (version >= 20) { int count = bR.ReadByte(); Dictionary tsigKeys = new Dictionary(count); for (int i = 0; i < count; i++) { string keyName = bR.ReadShortString(); string sharedSecret = bR.ReadShortString(); tsigKeys.Add(keyName, new TsigKey(keyName, sharedSecret, TsigAlgorithm.HMAC_SHA256)); } _dnsServer.TsigKeys = tsigKeys; } else { _dnsServer.TsigKeys = null; } if (version >= 22) _dnsServer.NsRevalidation = bR.ReadBoolean(); else _dnsServer.NsRevalidation = false; //default false since some badly configured websites fail to load break; default: throw new InvalidDataException("DNS Server config version not supported."); } } _log.Write("DNS Server config file was loaded: " + configFile); if (passwordResetOption) { SetCredentials("admin", "admin"); _log.Write("DNS Server reset password for user: admin"); SaveConfigFile(); try { File.Delete(configFile); } catch { } } if (version <= 6) SaveConfigFile(); //save as new config version to avoid loading old version next time } catch (FileNotFoundException) { _log.Write("DNS Server config file was not found: " + configFile); _log.Write("DNS Server is restoring default config file."); SetCredentials("admin", "admin"); _dnsServer.Recursion = DnsServerRecursion.AllowOnlyForPrivateNetworks; //default for security reasons _dnsServer.RandomizeName = true; //default true to enable security feature _dnsServer.QnameMinimization = true; //default true to enable privacy feature _dnsServer.NsRevalidation = false; //default false since some badly configured websites fail to load SaveConfigFile(); } catch (Exception ex) { _log.Write("DNS Server encountered an error while loading config file: " + configFile + "\r\n" + ex.ToString()); _log.Write("Note: You may try deleting the config file to fix this issue. However, you will lose DNS settings but, zone data wont be affected."); throw; } } private void SaveConfigFile() { string configFile = Path.Combine(_configFolder, "dns.config"); using (MemoryStream mS = new MemoryStream()) { //serialize config BinaryWriter bW = new BinaryWriter(mS); bW.Write(Encoding.ASCII.GetBytes("DS")); //format bW.Write((byte)22); //version bW.WriteShortString(_dnsServer.ServerDomain); bW.Write(_webServiceHttpPort); { bW.Write(Convert.ToByte(_webServiceLocalAddresses.Count)); foreach (IPAddress localAddress in _webServiceLocalAddresses) localAddress.WriteTo(bW); } bW.Write(_webServiceTlsPort); bW.Write(_webServiceEnableTls); bW.Write(_webServiceHttpToTlsRedirect); if (_webServiceTlsCertificatePath == null) bW.WriteShortString(string.Empty); else bW.WriteShortString(_webServiceTlsCertificatePath); if (_webServiceTlsCertificatePassword == null) bW.WriteShortString(string.Empty); else bW.WriteShortString(_webServiceTlsCertificatePassword); bW.Write(_dnsServer.PreferIPv6); bW.Write(_dnsServer.QueryLogManager != null); //logQueries bW.Write(_dnsServer.StatsManager.MaxStatFileDays); bW.Write((byte)_dnsServer.Recursion); if (_dnsServer.RecursionDeniedNetworks is null) { bW.Write((byte)0); } else { bW.Write(Convert.ToByte(_dnsServer.RecursionDeniedNetworks.Count)); foreach (NetworkAddress networkAddress in _dnsServer.RecursionDeniedNetworks) networkAddress.WriteTo(bW); } if (_dnsServer.RecursionAllowedNetworks is null) { bW.Write((byte)0); } else { bW.Write(Convert.ToByte(_dnsServer.RecursionAllowedNetworks.Count)); foreach (NetworkAddress networkAddress in _dnsServer.RecursionAllowedNetworks) networkAddress.WriteTo(bW); } bW.Write(_dnsServer.RandomizeName); bW.Write(_dnsServer.QnameMinimization); bW.Write(_dnsServer.QpmLimitRequests); bW.Write(_dnsServer.QpmLimitErrors); bW.Write(_dnsServer.QpmLimitSampleMinutes); bW.Write(_dnsServer.QpmLimitIPv4PrefixLength); bW.Write(_dnsServer.QpmLimitIPv6PrefixLength); bW.Write(_dnsServer.ServeStale); bW.Write(_dnsServer.CacheZoneManager.ServeStaleTtl); bW.Write(_dnsServer.CachePrefetchEligibility); bW.Write(_dnsServer.CachePrefetchTrigger); bW.Write(_dnsServer.CachePrefetchSampleIntervalInMinutes); bW.Write(_dnsServer.CachePrefetchSampleEligibilityHitsPerHour); if (_dnsServer.Proxy == null) { bW.Write((byte)NetProxyType.None); } else { bW.Write((byte)_dnsServer.Proxy.Type); bW.WriteShortString(_dnsServer.Proxy.Address); bW.Write(_dnsServer.Proxy.Port); NetworkCredential credential = _dnsServer.Proxy.Credential; if (credential == null) { bW.Write(false); } else { bW.Write(true); bW.WriteShortString(credential.UserName); bW.WriteShortString(credential.Password); } //bypass list { bW.Write(Convert.ToByte(_dnsServer.Proxy.BypassList.Count)); foreach (NetProxyBypassItem item in _dnsServer.Proxy.BypassList) bW.WriteShortString(item.Value); } } if (_dnsServer.Forwarders == null) { bW.Write((byte)0); } else { bW.Write(Convert.ToByte(_dnsServer.Forwarders.Count)); foreach (NameServerAddress forwarder in _dnsServer.Forwarders) forwarder.WriteTo(bW); } { bW.Write(Convert.ToByte(_credentials.Count)); foreach (KeyValuePair credential in _credentials) { bW.WriteShortString(credential.Key); bW.WriteShortString(credential.Value); } } //block list bW.Write(_dnsServer.EnableBlocking); bW.Write((byte)_dnsServer.BlockingType); { bW.Write(Convert.ToByte(_dnsServer.CustomBlockingARecords.Count + _dnsServer.CustomBlockingAAAARecords.Count)); foreach (DnsARecord record in _dnsServer.CustomBlockingARecords) record.Address.WriteTo(bW); foreach (DnsAAAARecord record in _dnsServer.CustomBlockingAAAARecords) record.Address.WriteTo(bW); } { bW.Write(Convert.ToByte(_dnsServer.BlockListZoneManager.AllowListUrls.Count + _dnsServer.BlockListZoneManager.BlockListUrls.Count)); foreach (Uri allowListUrl in _dnsServer.BlockListZoneManager.AllowListUrls) bW.WriteShortString("!" + allowListUrl.AbsoluteUri); foreach (Uri blockListUrl in _dnsServer.BlockListZoneManager.BlockListUrls) bW.WriteShortString(blockListUrl.AbsoluteUri); bW.Write(_blockListLastUpdatedOn); bW.Write(_blockListUpdateIntervalHours); } { bW.Write(Convert.ToByte(_dnsServer.LocalEndPoints.Count)); foreach (IPEndPoint localEP in _dnsServer.LocalEndPoints) localEP.WriteTo(bW); } bW.Write(_dnsServer.EnableDnsOverHttp); bW.Write(_dnsServer.EnableDnsOverTls); bW.Write(_dnsServer.EnableDnsOverHttps); if (_dnsTlsCertificatePath == null) bW.WriteShortString(string.Empty); else bW.WriteShortString(_dnsTlsCertificatePath); if (_dnsTlsCertificatePassword == null) bW.WriteShortString(string.Empty); else bW.WriteShortString(_dnsTlsCertificatePassword); bW.Write(_dnsServer.CacheZoneManager.MinimumRecordTtl); bW.Write(_dnsServer.CacheZoneManager.MaximumRecordTtl); bW.Write(_dnsServer.CacheZoneManager.NegativeRecordTtl); bW.Write(_dnsServer.CacheZoneManager.FailureRecordTtl); if (_dnsServer.TsigKeys is null) { bW.Write((byte)0); } else { bW.Write(Convert.ToByte(_dnsServer.TsigKeys.Count)); foreach (KeyValuePair tsigKey in _dnsServer.TsigKeys) { bW.WriteShortString(tsigKey.Key); bW.WriteShortString(tsigKey.Value.SharedSecret); bW.Write((byte)tsigKey.Value.Algorithm); } } bW.Write(_dnsServer.NsRevalidation); //write config mS.Position = 0; using (FileStream fS = new FileStream(configFile, FileMode.Create, FileAccess.Write)) { mS.CopyTo(fS); } } _log.Write("DNS Server config file was saved: " + configFile); } #endregion #region web service start stop private void StartDnsWebService() { int acceptTasks = Math.Max(1, Environment.ProcessorCount); //HTTP service try { string webServiceHostname = null; _webService = new HttpListener(); IPAddress httpAddress = null; foreach (IPAddress webServiceLocalAddress in _webServiceLocalAddresses) { string host; if (webServiceLocalAddress.Equals(IPAddress.Any)) { host = "+"; httpAddress = IPAddress.Loopback; } else if (webServiceLocalAddress.Equals(IPAddress.IPv6Any)) { host = "+"; if ((httpAddress == null) || !IPAddress.IsLoopback(httpAddress)) httpAddress = IPAddress.IPv6Loopback; } else { if (webServiceLocalAddress.AddressFamily == AddressFamily.InterNetworkV6) host = "[" + webServiceLocalAddress.ToString() + "]"; else host = webServiceLocalAddress.ToString(); if (httpAddress == null) httpAddress = webServiceLocalAddress; if (webServiceHostname == null) webServiceHostname = host; } _webService.Prefixes.Add("http://" + host + ":" + _webServiceHttpPort + "/"); } _webService.Start(); if (httpAddress == null) httpAddress = IPAddress.Loopback; _webServiceHttpEP = new IPEndPoint(httpAddress, _webServiceHttpPort); _webServiceHostname = webServiceHostname ?? Environment.MachineName.ToLower(); } catch (Exception ex) { _log.Write("Web Service failed to bind using default hostname. Attempting to bind again using 'localhost' hostname.\r\n" + ex.ToString()); try { _webService = new HttpListener(); _webService.Prefixes.Add("http://localhost:" + _webServiceHttpPort + "/"); _webService.Prefixes.Add("http://127.0.0.1:" + _webServiceHttpPort + "/"); _webService.Start(); } catch { _webService = new HttpListener(); _webService.Prefixes.Add("http://localhost:" + _webServiceHttpPort + "/"); _webService.Start(); } _webServiceHttpEP = new IPEndPoint(IPAddress.Loopback, _webServiceHttpPort); _webServiceHostname = "localhost"; } _webService.IgnoreWriteExceptions = true; for (int i = 0; i < acceptTasks; i++) { _ = Task.Factory.StartNew(delegate () { return AcceptWebRequestAsync(); }, CancellationToken.None, TaskCreationOptions.DenyChildAttach, _webServiceTaskScheduler); } _log.Write(new IPEndPoint(IPAddress.Any, _webServiceHttpPort), "HTTP Web Service was started successfully."); //TLS service if (_webServiceEnableTls && (_webServiceTlsCertificate != null)) { List webServiceTlsListeners = new List(); try { foreach (IPAddress webServiceLocalAddress in _webServiceLocalAddresses) { Socket tlsListener = new Socket(webServiceLocalAddress.AddressFamily, SocketType.Stream, ProtocolType.Tcp); tlsListener.Bind(new IPEndPoint(webServiceLocalAddress, _webServiceTlsPort)); tlsListener.Listen(10); webServiceTlsListeners.Add(tlsListener); } foreach (Socket tlsListener in webServiceTlsListeners) { for (int i = 0; i < acceptTasks; i++) { _ = Task.Factory.StartNew(delegate () { return AcceptTlsWebRequestAsync(tlsListener); }, CancellationToken.None, TaskCreationOptions.DenyChildAttach, _webServiceTaskScheduler); } } _webServiceTlsListeners = webServiceTlsListeners; _log.Write(new IPEndPoint(IPAddress.Any, _webServiceHttpPort), "TLS Web Service was started successfully."); } catch (Exception ex) { _log.Write("TLS Web Service failed to start.\r\n" + ex.ToString()); foreach (Socket tlsListener in webServiceTlsListeners) tlsListener.Dispose(); } } } private void StopDnsWebService() { _webService.Stop(); if (_webServiceTlsListeners != null) { foreach (Socket tlsListener in _webServiceTlsListeners) tlsListener.Dispose(); _webServiceTlsListeners = null; } } #endregion #endregion #region public public void Start() { if (_disposed) throw new ObjectDisposedException("WebService"); if (_state != ServiceState.Stopped) throw new InvalidOperationException("Web Service is already running."); _state = ServiceState.Starting; try { //get initial server domain string dnsServerDomain = Environment.MachineName.ToLower(); if (!DnsClient.IsDomainNameValid(dnsServerDomain)) dnsServerDomain = "dns-server-1"; //use this name instead since machine name is not a valid domain name //init dns server _dnsServer = new DnsServer(dnsServerDomain, _configFolder, Path.Combine(_appFolder, "dohwww"), _log); //init dhcp server _dhcpServer = new DhcpServer(Path.Combine(_configFolder, "scopes"), _log); _dhcpServer.AuthZoneManager = _dnsServer.AuthZoneManager; //load config LoadConfigFile(); //load all dns applications _dnsServer.DnsApplicationManager.LoadAllApplications(); //load all zones files _dnsServer.AuthZoneManager.LoadAllZoneFiles(); //disable zones from old config format if (_configDisabledZones != null) { foreach (string domain in _configDisabledZones) { AuthZoneInfo zoneInfo = _dnsServer.AuthZoneManager.GetAuthZoneInfo(domain); if (zoneInfo != null) { zoneInfo.Disabled = true; _dnsServer.AuthZoneManager.SaveZoneFile(zoneInfo.Name); } } } //load allowed zone and blocked zone _dnsServer.AllowedZoneManager.LoadAllowedZoneFile(); _dnsServer.BlockedZoneManager.LoadBlockedZoneFile(); //load block list zone async if (_dnsServer.BlockListZoneManager.BlockListUrls.Count > 0) { ThreadPool.QueueUserWorkItem(delegate (object state) { try { _dnsServer.BlockListZoneManager.LoadBlockLists(); StartBlockListUpdateTimer(); } catch (Exception ex) { _log.Write(ex); } }); } //start dns and dhcp _dnsServer.Start(); _dhcpServer.Start(); //start web service StartDnsWebService(); _state = ServiceState.Running; _log.Write("DNS Server (v" + _currentVersion.ToString() + ") was started successfully."); } catch (Exception ex) { _log.Write("Failed to start DNS Server (v" + _currentVersion.ToString() + ")\r\n" + ex.ToString()); throw; } } public void Stop() { if (_state != ServiceState.Running) return; _state = ServiceState.Stopping; try { StopDnsWebService(); _dnsServer.Dispose(); _dhcpServer.Dispose(); StopBlockListUpdateTimer(); StopTlsCertificateUpdateTimer(); if (_temporaryDisableBlockingTimer is not null) _temporaryDisableBlockingTimer.Dispose(); _state = ServiceState.Stopped; _log.Write("DNS Server (v" + _currentVersion.ToString() + ") was stopped successfully."); } catch (Exception ex) { _log.Write("Failed to stop DNS Server (v" + _currentVersion.ToString() + ")\r\n" + ex.ToString()); throw; } } #endregion #region properties public string ConfigFolder { get { return _configFolder; } } public int WebServiceHttpPort { get { return _webServiceHttpPort; } } public string WebServiceHostname { get { return _webServiceHostname; } } #endregion } }