diff --git a/DnsServerCore/InvalidTokenWebServiceException.cs b/DnsServerCore/InvalidTokenWebServiceException.cs
new file mode 100644
index 00000000..f244c681
--- /dev/null
+++ b/DnsServerCore/InvalidTokenWebServiceException.cs
@@ -0,0 +1,46 @@
+/*
+Technitium DNS Server
+Copyright (C) 2019 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 System;
+
+namespace DnsServerCore
+{
+ public class InvalidTokenWebServiceException : WebServiceException
+ {
+ #region constructors
+
+ public InvalidTokenWebServiceException()
+ : base()
+ { }
+
+ public InvalidTokenWebServiceException(string message)
+ : base(message)
+ { }
+
+ public InvalidTokenWebServiceException(string message, Exception innerException)
+ : base(message, innerException)
+ { }
+
+ protected InvalidTokenWebServiceException(System.Runtime.Serialization.SerializationInfo info, System.Runtime.Serialization.StreamingContext context)
+ : base(info, context)
+ { }
+
+ #endregion
+ }
+}
diff --git a/DnsServerCore/UserSession.cs b/DnsServerCore/UserSession.cs
new file mode 100644
index 00000000..961baa01
--- /dev/null
+++ b/DnsServerCore/UserSession.cs
@@ -0,0 +1,66 @@
+/*
+Technitium DNS Server
+Copyright (C) 2019 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 System;
+
+namespace DnsServerCore
+{
+ class UserSession
+ {
+ #region variables
+
+ const int SESSION_TIMEOUT = 30 * 60 * 1000; //30 mins
+
+ readonly string _username;
+ DateTime _lastSeen;
+
+ #endregion
+
+ #region constructor
+
+ public UserSession(string username)
+ {
+ _username = username;
+ _lastSeen = DateTime.UtcNow;
+ }
+
+ #endregion
+
+ #region public
+
+ public void UpdateLastSeen()
+ {
+ _lastSeen = DateTime.UtcNow;
+ }
+
+ public bool HasExpired()
+ {
+ return _lastSeen.AddMilliseconds(SESSION_TIMEOUT) < DateTime.UtcNow;
+ }
+
+ #endregion
+
+ #region properties
+
+ public string Username
+ { get { return _username; } }
+
+ #endregion
+ }
+}
diff --git a/DnsServerCore/WebService.cs b/DnsServerCore/WebService.cs
new file mode 100644
index 00000000..fd457470
--- /dev/null
+++ b/DnsServerCore/WebService.cs
@@ -0,0 +1,4567 @@
+/*
+Technitium DNS Server
+Copyright (C) 2019 Shreyas Zare (shreyas@technitium.com)
+
+This program is free software: you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation, either version 3 of the License, or
+(at your option) any later version.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program. If not, see .
+
+*/
+
+using DnsServerCore.Dhcp;
+using DnsServerCore.Dhcp.Options;
+using DnsServerCore.Dns;
+using Newtonsoft.Json;
+using Newtonsoft.Json.Converters;
+using System;
+using System.Collections.Concurrent;
+using System.Collections.Generic;
+using System.IO;
+using System.Net;
+using System.Reflection;
+using System.Security.Cryptography;
+using System.Security.Cryptography.X509Certificates;
+using System.Text;
+using System.Threading;
+using TechnitiumLibrary.IO;
+using TechnitiumLibrary.Net;
+using TechnitiumLibrary.Net.Dns;
+using TechnitiumLibrary.Net.Dns.ResourceRecords;
+using TechnitiumLibrary.Net.Proxy;
+
+namespace DnsServerCore
+{
+ public class WebService : IDisposable
+ {
+ #region enum
+
+ enum ServiceState
+ {
+ Stopped = 0,
+ Starting = 1,
+ Running = 2,
+ Stopping = 3
+ }
+
+ #endregion
+
+ #region variables
+
+ readonly string _currentVersion;
+ readonly string _appFolder;
+ readonly string _configFolder;
+ readonly Uri _updateCheckUri;
+
+ readonly LogManager _log;
+ StatsManager _stats;
+
+ DnsServer _dnsServer;
+ DhcpServer _dhcpServer;
+
+ int _webServicePort;
+ HttpListener _webService;
+ Thread _webServiceThread;
+ string _webServiceHostname;
+
+ string _tlsCertificatePath;
+ string _tlsCertificatePassword;
+ Timer _tlsCertificateUpdateTimer;
+ DateTime _tlsCertificateLastModifiedOn;
+ 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;
+
+ readonly Zone _customBlockedZoneRoot = new Zone(true);
+
+ Timer _blockListUpdateTimer;
+ readonly List _blockListUrls = new List();
+ DateTime _blockListLastUpdatedOn;
+ const int BLOCK_LIST_UPDATE_AFTER_HOURS = 24;
+ const int BLOCK_LIST_UPDATE_TIMER_INITIAL_INTERVAL = 5000;
+ const int BLOCK_LIST_UPDATE_TIMER_INTERVAL = 900000;
+ const int BLOCK_LIST_UPDATE_RETRIES = 3;
+
+ int _totalZonesAllowed;
+ int _totalZonesBlocked;
+
+ List _configDisabledZones;
+
+ #endregion
+
+ #region constructor
+
+ public WebService(string configFolder = null, Uri updateCheckUri = null)
+ {
+ Assembly assembly = Assembly.GetEntryAssembly();
+ AssemblyName assemblyName = assembly.GetName();
+
+ _currentVersion = assemblyName.Version.ToString();
+ _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;
+
+ string logFolder = Path.Combine(_configFolder, "logs");
+
+ if (!Directory.Exists(logFolder))
+ Directory.CreateDirectory(logFolder);
+
+ _log = new LogManager(logFolder);
+
+ string blockListsFolder = Path.Combine(_configFolder, "blocklists");
+
+ if (!Directory.Exists(blockListsFolder))
+ Directory.CreateDirectory(blockListsFolder);
+ }
+
+ #endregion
+
+ #region IDisposable
+
+ private bool _disposed = false;
+
+ protected virtual void Dispose(bool disposing)
+ {
+ if (_disposed)
+ return;
+
+ if (disposing)
+ {
+ Stop();
+
+ if (_dnsServer != null)
+ _dnsServer.Dispose();
+
+ if (_log != null)
+ _log.Dispose();
+
+ if (_stats != null)
+ _stats.Dispose();
+ }
+
+ _disposed = true;
+ }
+
+ public void Dispose()
+ {
+ Dispose(true);
+ }
+
+ #endregion
+
+ #region private
+
+ private void AcceptWebRequestAsync(object state)
+ {
+ try
+ {
+ while (true)
+ {
+ HttpListenerContext context = _webService.GetContext();
+ ThreadPool.QueueUserWorkItem(ProcessRequestAsync, new object[] { context.Request, context.Response });
+ }
+ }
+ catch (Exception ex)
+ {
+ if ((_state == ServiceState.Stopping) || (_state == ServiceState.Stopped))
+ return; //web service stopping
+
+ _log.Write(ex);
+
+ throw;
+ }
+ }
+
+ private void ProcessRequestAsync(object state)
+ {
+ object[] parameters = state as object[];
+ HttpListenerRequest request = parameters[0] as HttpListenerRequest;
+ HttpListenerResponse response = parameters[1] as HttpListenerResponse;
+
+ response.AddHeader("Server", "");
+ response.AddHeader("X-Robots-Tag", "noindex, nofollow");
+
+ try
+ {
+ Uri url = request.Url;
+ string path = url.AbsolutePath;
+
+ if (!path.StartsWith("/"))
+ {
+ SendError(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":
+ Login(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":
+ CheckForUpdate(request, jsonWriter);
+ break;
+
+ case "/api/getDnsSettings":
+ GetDnsSettings(jsonWriter);
+ break;
+
+ case "/api/setDnsSettings":
+ SetDnsSettings(request, jsonWriter);
+ break;
+
+ case "/api/getStats":
+ GetStats(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/flushAllowedZone":
+ FlushAllowedZone(request);
+ break;
+
+ case "/api/deleteAllowedZone":
+ DeleteAllowedZone(request);
+ break;
+
+ case "/api/allowZone":
+ AllowZone(request);
+ break;
+
+ case "/api/listBlockedZones":
+ ListBlockedZones(request, jsonWriter);
+ break;
+
+ case "/api/importCustomBlockedZones":
+ ImportCustomBlockedZones(request);
+ break;
+
+ case "/api/exportCustomBlockedZones":
+ ExportCustomBlockedZones(response);
+ return;
+
+ case "/api/flushCustomBlockedZone":
+ FlushCustomBlockedZone(request);
+ break;
+
+ case "/api/deleteCustomBlockedZone":
+ DeleteCustomBlockedZone(request);
+ break;
+
+ case "/api/customBlockZone":
+ CustomBlockZone(request);
+ break;
+
+ case "/api/listZones":
+ ListZones(jsonWriter);
+ break;
+
+ case "/api/createZone":
+ CreateZone(request, jsonWriter);
+ break;
+
+ case "/api/deleteZone":
+ DeleteZone(request);
+ break;
+
+ case "/api/enableZone":
+ EnableZone(request);
+ break;
+
+ case "/api/disableZone":
+ DisableZone(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/resolveQuery":
+ ResolveQuery(request, jsonWriter);
+ break;
+
+ case "/api/listLogs":
+ ListLogs(jsonWriter);
+ break;
+
+ case "/api/deleteLog":
+ DeleteLog(request);
+ break;
+
+ case "/api/listDhcpScopes":
+ ListDhcpScopes(jsonWriter);
+ break;
+
+ case "/api/listDhcpLeases":
+ ListDhcpLeases(jsonWriter);
+ break;
+
+ case "/api/getDhcpScope":
+ GetDhcpScope(request, jsonWriter);
+ break;
+
+ case "/api/setDhcpScope":
+ SetDhcpScope(request);
+ break;
+
+ case "/api/enableDhcpScope":
+ EnableDhcpScope(request);
+ break;
+
+ case "/api/disableDhcpScope":
+ DisableDhcpScope(request);
+ break;
+
+ case "/api/deleteDhcpScope":
+ DeleteDhcpScope(request);
+ break;
+
+ default:
+ throw new WebServiceException("Invalid command: " + path);
+ }
+ }
+ 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)
+ {
+ mS.SetLength(0);
+ JsonTextWriter jsonWriter = new JsonTextWriter(new StreamWriter(mS));
+ jsonWriter.WriteStartObject();
+
+ _log.Write(GetRequestRemoteEndPoint(request), ex);
+
+ jsonWriter.WritePropertyName("status");
+ jsonWriter.WriteValue("error");
+
+ jsonWriter.WritePropertyName("errorMessage");
+ jsonWriter.WriteValue(ex.Message);
+
+ jsonWriter.WritePropertyName("stackTrace");
+ jsonWriter.WriteValue(ex.StackTrace);
+
+ jsonWriter.WriteEndObject();
+ jsonWriter.Flush();
+ }
+
+ response.ContentType = "application/json; charset=utf-8";
+ response.ContentEncoding = Encoding.UTF8;
+ response.ContentLength64 = mS.Length;
+
+ using (Stream stream = response.OutputStream)
+ {
+ mS.WriteTo(response.OutputStream);
+ }
+ }
+ }
+ else if (path.StartsWith("/log/"))
+ {
+ if (!IsSessionValid(request))
+ {
+ SendError(response, 403, "Invalid token or session expired.");
+ return;
+ }
+
+ string[] pathParts = path.Split('/');
+
+ string logFileName = pathParts[2];
+ string logFile = Path.Combine(_log.LogFolder, logFileName + ".log");
+
+ int limit = 0;
+ string strLimit = request.QueryString["limit"];
+ if (!string.IsNullOrEmpty(strLimit))
+ limit = int.Parse(strLimit);
+
+ LogManager.DownloadLog(response, logFile, limit * 1024 * 1024);
+ }
+ else
+ {
+ if (path.Contains("/../"))
+ {
+ SendError(response, 404);
+ return;
+ }
+
+ if (path == "/blocklist.txt")
+ {
+ if (!IPAddress.IsLoopback(GetRequestRemoteEndPoint(request).Address))
+ SendError(response, 403);
+ }
+
+ if (path == "/")
+ path = "/index.html";
+
+ path = Path.Combine(_appFolder, "www" + path.Replace('/', Path.DirectorySeparatorChar));
+
+ if (!File.Exists(path))
+ {
+ SendError(response, 404);
+ return;
+ }
+
+ SendFile(response, path);
+ }
+ }
+ catch (Exception ex)
+ {
+ if ((_state == ServiceState.Stopping) || (_state == ServiceState.Stopped))
+ return; //web service stopping
+
+ _log.Write(GetRequestRemoteEndPoint(request), ex);
+
+ SendError(response, ex);
+ }
+ }
+
+ private IPEndPoint GetRequestRemoteEndPoint(HttpListenerRequest request)
+ {
+ //this is due to mono NullReferenceException issue
+ try
+ {
+ if (NetUtilities.IsPrivateIP(request.RemoteEndPoint.Address))
+ {
+ //reverse proxy X-Real-IP header supported only when remote IP address is private
+
+ string xRealIp = request.Headers["X-Real-IP"];
+ if (!string.IsNullOrEmpty(xRealIp))
+ {
+ //get the real IP address of the requesting client from X-Real-IP header set in nginx proxy_pass block
+ return new IPEndPoint(IPAddress.Parse(xRealIp), 0);
+ }
+ }
+
+ return request.RemoteEndPoint;
+ }
+ catch
+ {
+ return new IPEndPoint(IPAddress.Any, 0);
+ }
+ }
+
+ private static void SendError(HttpListenerResponse response, Exception ex)
+ {
+ SendError(response, 500, ex.ToString());
+ }
+
+ private static void SendError(HttpListenerResponse response, int statusCode, string message = null)
+ {
+ try
+ {
+ string statusString = statusCode + " " + DnsServer.GetStatusString((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)
+ {
+ stream.Write(buffer, 0, buffer.Length);
+ }
+ }
+ catch
+ { }
+ }
+
+ private static void SendFile(HttpListenerResponse response, string path)
+ {
+ using (FileStream fS = new FileStream(path, FileMode.Open, FileAccess.Read))
+ {
+ response.ContentType = WebUtilities.GetContentType(path).MediaType;
+ response.ContentLength64 = fS.Length;
+ response.AddHeader("Cache-Control", "private, max-age=300");
+
+ using (Stream stream = response.OutputStream)
+ {
+ try
+ {
+ fS.CopyTo(stream);
+ }
+ catch (HttpListenerException)
+ {
+ //ignore this error
+ }
+ }
+ }
+ }
+
+ private string CreateSession(string username)
+ {
+ string token = BinaryNumber.GenerateRandomNumber256().ToString();
+
+ if (!_sessions.TryAdd(token, new UserSession(username)))
+ throw new WebServiceException("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 WebServiceException("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 WebServiceException("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 _);
+ }
+
+ private void Login(HttpListenerRequest request, JsonTextWriter jsonWriter)
+ {
+ string strUsername = request.QueryString["user"];
+ if (string.IsNullOrEmpty(strUsername))
+ throw new WebServiceException("Parameter 'user' missing.");
+
+ string strPassword = request.QueryString["pass"];
+ if (string.IsNullOrEmpty(strPassword))
+ throw new WebServiceException("Parameter 'pass' missing.");
+
+ IPEndPoint remoteEP = GetRequestRemoteEndPoint(request);
+
+ if (IsAddressBlocked(remoteEP.Address))
+ throw new WebServiceException("Max limit of " + MAX_LOGIN_ATTEMPTS + " attempts exceeded. Access blocked for " + (BLOCK_ADDRESS_INTERVAL / 1000) + " seconds.");
+
+ strUsername = strUsername.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);
+
+ Thread.Sleep(1000);
+ }
+
+ throw new WebServiceException("Invalid username or password: " + 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 WebServiceException("Parameter 'token' missing.");
+
+ string strPassword = request.QueryString["pass"];
+ if (string.IsNullOrEmpty(strPassword))
+ throw new WebServiceException("Parameter 'pass' missing.");
+
+ UserSession session = GetSession(strToken);
+ if (session == null)
+ throw new WebServiceException("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 WebServiceException("Parameter 'token' missing.");
+
+ UserSession session = DeleteSession(strToken);
+
+ if (session != null)
+ _log.Write(GetRequestRemoteEndPoint(request), "[" + session.Username + "] User logged out.");
+ }
+
+ 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);
+ }
+
+ public static void CreateUpdateInfov1(Stream s, string version, string displayText, string downloadLink)
+ {
+ BincodingEncoder encoder = new BincodingEncoder(s, "DU", 1);
+
+ encoder.EncodeKeyValue("version", version);
+ encoder.EncodeKeyValue("displayText", displayText);
+ encoder.EncodeKeyValue("downloadLink", downloadLink);
+ }
+
+ private void CheckForUpdate(HttpListenerRequest request, JsonTextWriter jsonWriter)
+ {
+ string updateVersion = null;
+ string displayText = null;
+ string downloadLink = null;
+
+ bool updateAvailable = false;
+
+ if (_updateCheckUri != null)
+ {
+ try
+ {
+ using (WebClientEx wc = new WebClientEx())
+ {
+ wc.Proxy = _dnsServer.Proxy;
+
+ byte[] response = wc.DownloadData(_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 1:
+ #region old version
+
+ mS.Position = 0;
+ BincodingDecoder decoder = new BincodingDecoder(mS, "DU");
+
+ switch (decoder.Version)
+ {
+ case 1:
+ while (true)
+ {
+ Bincoding entry = decoder.DecodeNext();
+ if (entry == null)
+ break;
+
+ KeyValuePair value = entry.GetKeyValuePair();
+
+ switch (value.Key)
+ {
+ case "version":
+ updateVersion = value.Value.GetStringValue();
+ break;
+
+ case "displayText":
+ displayText = value.Value.GetStringValue();
+ break;
+
+ case "downloadLink":
+ downloadLink = value.Value.GetStringValue();
+ break;
+ }
+ }
+ break;
+
+ default:
+ throw new IOException("File version not supported: " + decoder.Version);
+ }
+
+ #endregion
+ break;
+
+ case 2:
+ updateVersion = bR.ReadShortString();
+ displayText = bR.ReadShortString();
+ downloadLink = bR.ReadShortString();
+ break;
+
+ default:
+ throw new InvalidDataException("DNS Server update info version not supported.");
+ }
+
+ updateAvailable = IsUpdateAvailable(_currentVersion, updateVersion);
+ }
+ }
+
+ _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 bool IsUpdateAvailable(string currentVersion, string updateVersion)
+ {
+ if (updateVersion == null)
+ return false;
+
+ string[] uVer = updateVersion.Split(new char[] { '.' });
+ string[] cVer = currentVersion.Split(new char[] { '.' });
+
+ int x = uVer.Length;
+ if (x > cVer.Length)
+ x = cVer.Length;
+
+ for (int i = 0; i < x; i++)
+ {
+ if (Convert.ToInt32(uVer[i]) > Convert.ToInt32(cVer[i]))
+ return true;
+ else if (Convert.ToInt32(uVer[i]) < Convert.ToInt32(cVer[i]))
+ return false;
+ }
+
+ if (uVer.Length > cVer.Length)
+ {
+ for (int i = x; i < uVer.Length; i++)
+ {
+ if (Convert.ToInt32(uVer[i]) > 0)
+ return true;
+ }
+ }
+
+ return false;
+ }
+
+ private static string GetCleanVersion(string version)
+ {
+ while (version.EndsWith(".0"))
+ {
+ version = version.Substring(0, version.Length - 2);
+ }
+
+ return version;
+ }
+
+ private void GetDnsSettings(JsonTextWriter jsonWriter)
+ {
+ jsonWriter.WritePropertyName("version");
+ jsonWriter.WriteValue(GetCleanVersion(_currentVersion));
+
+ jsonWriter.WritePropertyName("serverDomain");
+ jsonWriter.WriteValue(_dnsServer.ServerDomain);
+
+ jsonWriter.WritePropertyName("webServicePort");
+ jsonWriter.WriteValue(_webServicePort);
+
+ jsonWriter.WritePropertyName("dnsServerLocalAddresses");
+ jsonWriter.WriteStartArray();
+
+ foreach (IPAddress localAddress in _dnsServer.LocalAddresses)
+ jsonWriter.WriteValue(localAddress.ToString());
+
+ jsonWriter.WriteEndArray();
+
+ jsonWriter.WritePropertyName("enableDnsOverHttp");
+ jsonWriter.WriteValue(_dnsServer.EnableDnsOverHttp);
+
+ jsonWriter.WritePropertyName("enableDnsOverTls");
+ jsonWriter.WriteValue(_dnsServer.EnableDnsOverTls);
+
+ jsonWriter.WritePropertyName("enableDnsOverHttps");
+ jsonWriter.WriteValue(_dnsServer.EnableDnsOverHttps);
+
+ jsonWriter.WritePropertyName("tlsCertificatePath");
+ jsonWriter.WriteValue(_tlsCertificatePath);
+
+ jsonWriter.WritePropertyName("tlsCertificatePassword");
+ jsonWriter.WriteValue("************");
+
+ jsonWriter.WritePropertyName("preferIPv6");
+ jsonWriter.WriteValue(_dnsServer.PreferIPv6);
+
+ jsonWriter.WritePropertyName("logQueries");
+ jsonWriter.WriteValue(_dnsServer.QueryLogManager != null);
+
+ jsonWriter.WritePropertyName("allowRecursion");
+ jsonWriter.WriteValue(_dnsServer.AllowRecursion);
+
+ jsonWriter.WritePropertyName("allowRecursionOnlyForPrivateNetworks");
+ jsonWriter.WriteValue(_dnsServer.AllowRecursionOnlyForPrivateNetworks);
+
+ 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.WriteEndObject();
+ }
+
+ jsonWriter.WritePropertyName("forwarders");
+
+ if (_dnsServer.Forwarders == null)
+ {
+ jsonWriter.WriteNull();
+ }
+ else
+ {
+ jsonWriter.WriteStartArray();
+
+ foreach (NameServerAddress forwarder in _dnsServer.Forwarders)
+ jsonWriter.WriteValue(forwarder.OriginalString);
+
+ jsonWriter.WriteEndArray();
+ }
+
+ jsonWriter.WritePropertyName("forwarderProtocol");
+ jsonWriter.WriteValue(_dnsServer.ForwarderProtocol.ToString());
+
+
+ jsonWriter.WritePropertyName("blockListUrls");
+
+ if (_blockListUrls.Count == 0)
+ {
+ jsonWriter.WriteNull();
+ }
+ else
+ {
+ jsonWriter.WriteStartArray();
+
+ foreach (Uri blockListUrl in _blockListUrls)
+ jsonWriter.WriteValue(blockListUrl.AbsoluteUri);
+
+ jsonWriter.WriteEndArray();
+ }
+ }
+
+ private void SetDnsSettings(HttpListenerRequest request, JsonTextWriter jsonWriter)
+ {
+ string strServerDomain = request.QueryString["serverDomain"];
+ if (!string.IsNullOrEmpty(strServerDomain))
+ {
+ strServerDomain = strServerDomain.ToLower();
+
+ if (_dnsServer.ServerDomain != strServerDomain)
+ {
+ string oldServerDomain = _dnsServer.ServerDomain;
+ _dnsServer.ServerDomain = strServerDomain;
+
+ ThreadPool.QueueUserWorkItem(delegate (object state)
+ {
+ try
+ {
+ //authoritative zone
+ {
+ ICollection zones = _dnsServer.AuthoritativeZoneRoot.ListAuthoritativeZones();
+
+ foreach (ZoneInfo zone in zones)
+ {
+ DnsResourceRecord[] soaResourceRecords = _dnsServer.AuthoritativeZoneRoot.GetAllRecords(zone.ZoneName, DnsResourceRecordType.SOA, false, true);
+ if (soaResourceRecords.Length > 0)
+ {
+ DnsResourceRecord soaRecord = soaResourceRecords[0];
+ DnsSOARecord soaRecordData = soaRecord.RDATA as DnsSOARecord;
+
+ if (soaRecordData.MasterNameServer.Equals(oldServerDomain, StringComparison.OrdinalIgnoreCase))
+ {
+ string responsiblePerson = soaRecordData.ResponsiblePerson;
+ if (responsiblePerson.EndsWith(oldServerDomain))
+ responsiblePerson = responsiblePerson.Replace(oldServerDomain, strServerDomain);
+
+ _dnsServer.AuthoritativeZoneRoot.SetRecords(soaRecord.Name, soaRecord.Type, soaRecord.TtlValue, new DnsResourceRecordData[] { new DnsSOARecord(strServerDomain, responsiblePerson, soaRecordData.Serial, soaRecordData.Refresh, soaRecordData.Retry, soaRecordData.Expire, soaRecordData.Minimum) });
+
+ //update NS records
+ DnsResourceRecord[] nsResourceRecords = _dnsServer.AuthoritativeZoneRoot.GetAllRecords(zone.ZoneName, DnsResourceRecordType.NS, false, true);
+
+ foreach (DnsResourceRecord nsResourceRecord in nsResourceRecords)
+ {
+ if ((nsResourceRecord.RDATA as DnsNSRecord).NSDomainName.Equals(oldServerDomain, StringComparison.OrdinalIgnoreCase))
+ _dnsServer.AuthoritativeZoneRoot.UpdateRecord(nsResourceRecord, new DnsResourceRecord(nsResourceRecord.Name, nsResourceRecord.Type, nsResourceRecord.Class, nsResourceRecord.TtlValue, new DnsNSRecord(strServerDomain)));
+ }
+
+ try
+ {
+ SaveZoneFile(zone.ZoneName);
+ }
+ catch (Exception ex)
+ {
+ _log.Write(ex);
+ }
+ }
+ }
+ }
+ }
+
+ //allowed zone
+ {
+ ICollection zones = _dnsServer.AllowedZoneRoot.ListAuthoritativeZones();
+
+ foreach (ZoneInfo zone in zones)
+ {
+ DnsResourceRecord[] soaResourceRecords = _dnsServer.AllowedZoneRoot.GetAllRecords(zone.ZoneName, DnsResourceRecordType.SOA, false, true);
+ if (soaResourceRecords.Length > 0)
+ {
+ DnsResourceRecord soaRecord = soaResourceRecords[0];
+ DnsSOARecord soaRecordData = soaRecord.RDATA as DnsSOARecord;
+
+ _dnsServer.AllowedZoneRoot.SetRecords(soaRecord.Name, soaRecord.Type, soaRecord.TtlValue, new DnsResourceRecordData[] { new DnsSOARecord(strServerDomain, "hostmaster." + strServerDomain, soaRecordData.Serial, soaRecordData.Refresh, soaRecordData.Retry, soaRecordData.Expire, soaRecordData.Minimum) });
+ }
+ }
+ }
+
+ //custom blocked zone
+ {
+ ICollection zones = _customBlockedZoneRoot.ListAuthoritativeZones();
+
+ foreach (ZoneInfo zone in zones)
+ {
+ DnsResourceRecord[] soaResourceRecords = _customBlockedZoneRoot.GetAllRecords(zone.ZoneName, DnsResourceRecordType.SOA, false, true);
+ if (soaResourceRecords.Length > 0)
+ {
+ DnsResourceRecord soaRecord = soaResourceRecords[0];
+ DnsSOARecord soaRecordData = soaRecord.RDATA as DnsSOARecord;
+
+ _customBlockedZoneRoot.SetRecords(soaRecord.Name, soaRecord.Type, soaRecord.TtlValue, new DnsResourceRecordData[] { new DnsSOARecord(strServerDomain, "hostmaster." + strServerDomain, soaRecordData.Serial, soaRecordData.Refresh, soaRecordData.Retry, soaRecordData.Expire, soaRecordData.Minimum) });
+ }
+ }
+ }
+
+ //blocked zone
+ {
+ ICollection zones = _dnsServer.BlockedZoneRoot.ListAuthoritativeZones();
+
+ foreach (ZoneInfo zone in zones)
+ {
+ DnsResourceRecord[] soaResourceRecords = _dnsServer.BlockedZoneRoot.GetAllRecords(zone.ZoneName, DnsResourceRecordType.SOA, false, true);
+ if (soaResourceRecords.Length > 0)
+ {
+ DnsResourceRecord soaRecord = soaResourceRecords[0];
+ DnsSOARecord soaRecordData = soaRecord.RDATA as DnsSOARecord;
+
+ _dnsServer.BlockedZoneRoot.SetRecords(soaRecord.Name, soaRecord.Type, soaRecord.TtlValue, new DnsResourceRecordData[] { new DnsSOARecord(strServerDomain, "hostmaster." + strServerDomain, soaRecordData.Serial, soaRecordData.Refresh, soaRecordData.Retry, soaRecordData.Expire, soaRecordData.Minimum) });
+ }
+ }
+ }
+ }
+ catch (Exception ex)
+ {
+ _log.Write(ex);
+ }
+ });
+ }
+ }
+
+ string strDnsServerLocalAddresses = request.QueryString["dnsServerLocalAddresses"];
+ if (strDnsServerLocalAddresses != null)
+ {
+ if (string.IsNullOrEmpty(strDnsServerLocalAddresses))
+ strDnsServerLocalAddresses = "0.0.0.0,::";
+
+ string[] strLocalAddresses = strDnsServerLocalAddresses.Split(new char[] { ',' }, StringSplitOptions.RemoveEmptyEntries);
+ IPAddress[] localAddresses = new IPAddress[strLocalAddresses.Length];
+
+ for (int i = 0; i < strLocalAddresses.Length; i++)
+ localAddresses[i] = IPAddress.Parse(strLocalAddresses[i]);
+
+ _dnsServer.LocalAddresses = localAddresses;
+ }
+
+ int oldWebServicePort = _webServicePort;
+
+ string strWebServicePort = request.QueryString["webServicePort"];
+ if (!string.IsNullOrEmpty(strWebServicePort))
+ _webServicePort = int.Parse(strWebServicePort);
+
+ string enableDnsOverHttp = request.QueryString["enableDnsOverHttp"];
+ if (!string.IsNullOrEmpty(enableDnsOverHttp))
+ _dnsServer.EnableDnsOverHttp = bool.Parse(enableDnsOverHttp);
+
+ string strEnableDnsOverTls = request.QueryString["enableDnsOverTls"];
+ if (!string.IsNullOrEmpty(strEnableDnsOverTls))
+ _dnsServer.EnableDnsOverTls = bool.Parse(strEnableDnsOverTls);
+
+ string strEnableDnsOverHttps = request.QueryString["enableDnsOverHttps"];
+ if (!string.IsNullOrEmpty(strEnableDnsOverHttps))
+ _dnsServer.EnableDnsOverHttps = bool.Parse(strEnableDnsOverHttps);
+
+ string strTlsCertificatePath = request.QueryString["tlsCertificatePath"];
+ string strTlsCertificatePassword = request.QueryString["tlsCertificatePassword"];
+ if (string.IsNullOrEmpty(strTlsCertificatePath))
+ {
+ StopTlsCertificateUpdateTimer();
+ _tlsCertificatePath = null;
+ _tlsCertificatePassword = "";
+ }
+ else
+ {
+ if (strTlsCertificatePassword == "************")
+ strTlsCertificatePassword = _tlsCertificatePassword;
+
+ if ((strTlsCertificatePath != _tlsCertificatePath) || (strTlsCertificatePassword != _tlsCertificatePassword))
+ {
+ LoadTlsCertificate(strTlsCertificatePath, strTlsCertificatePassword);
+
+ _tlsCertificatePath = strTlsCertificatePath;
+ _tlsCertificatePassword = strTlsCertificatePassword;
+
+ StartTlsCertificateUpdateTimer();
+ }
+ }
+
+ string strPreferIPv6 = request.QueryString["preferIPv6"];
+ if (!string.IsNullOrEmpty(strPreferIPv6))
+ _dnsServer.PreferIPv6 = bool.Parse(strPreferIPv6);
+
+ string strLogQueries = request.QueryString["logQueries"];
+ if (!string.IsNullOrEmpty(strLogQueries))
+ {
+ if (bool.Parse(strLogQueries))
+ _dnsServer.QueryLogManager = _log;
+ else
+ _dnsServer.QueryLogManager = null;
+ }
+
+ string strAllowRecursion = request.QueryString["allowRecursion"];
+ if (!string.IsNullOrEmpty(strAllowRecursion))
+ _dnsServer.AllowRecursion = bool.Parse(strAllowRecursion);
+
+ string strAllowRecursionOnlyForPrivateNetworks = request.QueryString["allowRecursionOnlyForPrivateNetworks"];
+ if (!string.IsNullOrEmpty(strAllowRecursionOnlyForPrivateNetworks))
+ _dnsServer.AllowRecursionOnlyForPrivateNetworks = bool.Parse(strAllowRecursionOnlyForPrivateNetworks);
+
+ 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 = new NetProxy(proxyType, request.QueryString["proxyAddress"], int.Parse(request.QueryString["proxyPort"]), credential);
+ }
+ }
+
+ 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++)
+ forwarders[i] = new NameServerAddress(strForwardersList[i]);
+
+ _dnsServer.Forwarders = forwarders;
+ }
+ }
+
+ string strForwarderProtocol = request.QueryString["forwarderProtocol"];
+ if (!string.IsNullOrEmpty(strForwarderProtocol))
+ _dnsServer.ForwarderProtocol = (DnsTransportProtocol)Enum.Parse(typeof(DnsTransportProtocol), strForwarderProtocol, true);
+
+ string strBlockListUrls = request.QueryString["blockListUrls"];
+ if (!string.IsNullOrEmpty(strBlockListUrls))
+ {
+ if (strBlockListUrls == "false")
+ {
+ StopBlockListUpdateTimer();
+ FlushBlockedZone(request);
+
+ _blockListUrls.Clear();
+ }
+ else
+ {
+ bool updated = false;
+
+ string[] strBlockListUrlList = strBlockListUrls.Split(new char[] { ',' }, StringSplitOptions.RemoveEmptyEntries);
+
+ if (oldWebServicePort != _webServicePort)
+ {
+ for (int i = 0; i < strBlockListUrlList.Length; i++)
+ {
+ if (strBlockListUrlList[i].Contains("http://localhost:" + oldWebServicePort + "/blocklist.txt"))
+ {
+ strBlockListUrlList[i] = "http://localhost:" + _webServicePort + "/blocklist.txt";
+ updated = true;
+ break;
+ }
+ }
+ }
+
+ if (!updated)
+ {
+ if (strBlockListUrlList.Length != _blockListUrls.Count)
+ {
+ updated = true;
+ }
+ else
+ {
+ foreach (string strBlockListUrl in strBlockListUrlList)
+ {
+ if (!_blockListUrls.Contains(new Uri(strBlockListUrl)))
+ {
+ updated = true;
+ break;
+ }
+ }
+ }
+ }
+
+ if (updated)
+ {
+ _blockListUrls.Clear();
+
+ foreach (string strBlockListUrl in strBlockListUrlList)
+ _blockListUrls.Add(new Uri(strBlockListUrl));
+
+ _blockListLastUpdatedOn = new DateTime();
+
+ StopBlockListUpdateTimer();
+ StartBlockListUpdateTimer();
+ }
+ }
+ }
+
+ _log.Write(GetRequestRemoteEndPoint(request), "[" + GetSession(request).Username + "] DNS Settings were updated {serverDomain: " + _dnsServer.ServerDomain + "; dnsServerLocalAddresses: " + strDnsServerLocalAddresses + "; webServicePort: " + _webServicePort + "; enableDnsOverHttp: " + _dnsServer.EnableDnsOverHttp + "; enableDnsOverTls: " + _dnsServer.EnableDnsOverTls + "; enableDnsOverHttps: " + _dnsServer.EnableDnsOverHttps + "; tlsCertificatePath: " + _tlsCertificatePath + "; preferIPv6: " + _dnsServer.PreferIPv6 + "; logQueries: " + (_dnsServer.QueryLogManager != null) + "; allowRecursion: " + _dnsServer.AllowRecursion + "; allowRecursionOnlyForPrivateNetworks: " + _dnsServer.AllowRecursionOnlyForPrivateNetworks + "; proxyType: " + strProxyType + "; forwarders: " + strForwarders + "; forwarderProtocol: " + strForwarderProtocol + "; blockListUrl: " + strBlockListUrls + ";}");
+
+ SaveConfigFile();
+
+ GetDnsSettings(jsonWriter);
+ }
+
+ private void GetStats(HttpListenerRequest request, JsonTextWriter jsonWriter)
+ {
+ string strType = request.QueryString["type"];
+ if (string.IsNullOrEmpty(strType))
+ strType = "lastHour";
+
+ Dictionary>> data;
+
+ switch (strType)
+ {
+ case "lastHour":
+ data = _stats.GetLastHourStats();
+ break;
+
+ case "lastDay":
+ data = _stats.GetLastDayStats();
+ break;
+
+ case "lastWeek":
+ data = _stats.GetLastWeekStats();
+ break;
+
+ case "lastMonth":
+ data = _stats.GetLastMonthStats();
+ break;
+
+ case "lastYear":
+ data = _stats.GetLastYearStats();
+ break;
+
+ default:
+ throw new WebServiceException("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("allowedZones");
+ jsonWriter.WriteValue(_totalZonesAllowed);
+
+ jsonWriter.WritePropertyName("blockedZones");
+ jsonWriter.WriteValue(_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 Queries", "rgba(102, 153, 255, 0.1)", "rgb(102, 153, 255)", data["totalQueriesPerInterval"]);
+ WriteChartDataSet(jsonWriter, "Cache Hit", "rgba(111, 84, 153, 0.1)", "rgb(111, 84, 153)", data["totalCacheHitPerInterval"]);
+ 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, "Name Error", "rgba(7, 7, 7, 0.1)", "rgb(7, 7, 7)", data["totalNameErrorPerInterval"]);
+ WriteChartDataSet(jsonWriter, "Refused", "rgba(91, 192, 222, 0.1)", "rgb(91, 192, 222)", data["totalRefusedPerInterval"]);
+ 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 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(91, 192, 222, 0.5)");
+ jsonWriter.WriteValue("rgba(255, 165, 0, 0.5)");
+ jsonWriter.WriteValue("rgba(51, 122, 183, 0.5)");
+ jsonWriter.WriteEndArray();
+
+ jsonWriter.WriteEndObject();
+
+ jsonWriter.WriteEndArray();
+ }
+
+ jsonWriter.WriteEndObject();
+ }
+
+ //top clients
+ {
+ List> topClients = data["topClients"];
+
+ jsonWriter.WritePropertyName("topClients");
+ jsonWriter.WriteStartArray();
+
+ foreach (KeyValuePair item in topClients)
+ {
+ jsonWriter.WriteStartObject();
+
+ jsonWriter.WritePropertyName("name");
+ jsonWriter.WriteValue(item.Key);
+
+ 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 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 void FlushCache(HttpListenerRequest request)
+ {
+ _dnsServer.CacheZoneRoot.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"];
+
+ string[] subZones;
+ DnsResourceRecord[] records;
+
+ while (true)
+ {
+ subZones = _dnsServer.CacheZoneRoot.ListSubZones(domain);
+ records = _dnsServer.CacheZoneRoot.GetAllRecords(domain, DnsResourceRecordType.ANY, false);
+
+ if (records.Length > 0)
+ break;
+
+ if (subZones.Length != 1)
+ break;
+
+ if (direction == "up")
+ {
+ if (domain == "")
+ break;
+
+ int i = domain.IndexOf('.');
+ if (i < 0)
+ domain = "";
+ else
+ domain = domain.Substring(i + 1);
+ }
+ else if (domain == "")
+ {
+ domain = subZones[0];
+ }
+ else
+ {
+ domain = subZones[0] + "." + domain;
+ }
+ }
+
+ Array.Sort(subZones);
+
+ jsonWriter.WritePropertyName("domain");
+ jsonWriter.WriteValue(domain);
+
+ jsonWriter.WritePropertyName("zones");
+ jsonWriter.WriteStartArray();
+
+ if (domain != "")
+ 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 WebServiceException("Parameter 'domain' missing.");
+
+ _dnsServer.CacheZoneRoot.DeleteZone(domain, true);
+
+ _log.Write(GetRequestRemoteEndPoint(request), "[" + GetSession(request).Username + "] Cached zone was deleted: " + domain);
+ }
+
+ private void ListAllowedZones(HttpListenerRequest request, JsonTextWriter jsonWriter)
+ {
+ string domain = request.QueryString["domain"];
+ if (domain == null)
+ domain = "";
+
+ string direction = request.QueryString["direction"];
+
+ string[] subZones;
+ DnsResourceRecord[] records;
+
+ while (true)
+ {
+ subZones = _dnsServer.AllowedZoneRoot.ListSubZones(domain);
+ records = _dnsServer.AllowedZoneRoot.GetAllRecords(domain, DnsResourceRecordType.ANY, false);
+
+ if (records.Length > 0)
+ break;
+
+ if (subZones.Length != 1)
+ break;
+
+ if (direction == "up")
+ {
+ if (domain == "")
+ break;
+
+ int i = domain.IndexOf('.');
+ if (i < 0)
+ domain = "";
+ else
+ domain = domain.Substring(i + 1);
+ }
+ else if (domain == "")
+ {
+ domain = subZones[0];
+ }
+ else
+ {
+ domain = subZones[0] + "." + domain;
+ }
+ }
+
+ Array.Sort(subZones);
+
+ jsonWriter.WritePropertyName("domain");
+ jsonWriter.WriteValue(domain);
+
+ jsonWriter.WritePropertyName("zones");
+ jsonWriter.WriteStartArray();
+
+ if (domain != "")
+ domain = "." + domain;
+
+ foreach (string subZone in subZones)
+ jsonWriter.WriteValue(subZone + domain);
+
+ jsonWriter.WriteEndArray();
+
+ WriteRecordsAsJson(records, jsonWriter, false);
+ }
+
+ private void ImportAllowedZones(HttpListenerRequest request)
+ {
+ if (!request.ContentType.StartsWith("application/x-www-form-urlencoded"))
+ throw new WebServiceException("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(',');
+
+ foreach (string allowedZone in allowedZones)
+ AllowZone(allowedZone);
+
+ _log.Write(GetRequestRemoteEndPoint(request), "[" + GetSession(request).Username + "] Total " + allowedZones.Length + " zones were imported into allowed zone successfully.");
+ SaveAllowedZoneFile();
+ return;
+ }
+ }
+
+ throw new WebServiceException("Parameter 'allowedZones' missing.");
+ }
+
+ private void ExportAllowedZones(HttpListenerResponse response)
+ {
+ ICollection zoneInfoList = _dnsServer.AllowedZoneRoot.ListAuthoritativeZones();
+
+ response.ContentType = "text/plain";
+ response.AddHeader("Content-Disposition", "attachment;filename=AllowedZones.txt");
+
+ using (StreamWriter sW = new StreamWriter(new BufferedStream(response.OutputStream)))
+ {
+ foreach (ZoneInfo zoneInfo in zoneInfoList)
+ sW.WriteLine(zoneInfo.ZoneName);
+ }
+ }
+
+ private void FlushAllowedZone(HttpListenerRequest request)
+ {
+ _dnsServer.AllowedZoneRoot.Flush();
+
+ _log.Write(GetRequestRemoteEndPoint(request), "[" + GetSession(request).Username + "] Allowed zone was flushed.");
+
+ SaveAllowedZoneFile();
+ }
+
+ private void DeleteAllowedZone(HttpListenerRequest request)
+ {
+ string domain = request.QueryString["domain"];
+ if (string.IsNullOrEmpty(domain))
+ throw new WebServiceException("Parameter 'domain' missing.");
+
+ _dnsServer.AllowedZoneRoot.DeleteZone(domain, false);
+
+ _log.Write(GetRequestRemoteEndPoint(request), "[" + GetSession(request).Username + "] Allowed zone was deleted: " + domain);
+
+ SaveAllowedZoneFile();
+ }
+
+ private void AllowZone(HttpListenerRequest request)
+ {
+ string domain = request.QueryString["domain"];
+ if (string.IsNullOrEmpty(domain))
+ throw new WebServiceException("Parameter 'domain' missing.");
+
+ if (IPAddress.TryParse(domain, out IPAddress ipAddress))
+ domain = (new DnsQuestionRecord(ipAddress, DnsClass.IN)).Name;
+
+ AllowZone(domain);
+
+ _log.Write(GetRequestRemoteEndPoint(request), "[" + GetSession(request).Username + "] Zone was allowed: " + domain);
+ SaveAllowedZoneFile();
+ }
+
+ private void AllowZone(string domain)
+ {
+ _dnsServer.AllowedZoneRoot.SetRecords(domain, DnsResourceRecordType.SOA, 60, new DnsResourceRecordData[] { new DnsSOARecord(_dnsServer.ServerDomain, "hostmaster." + _dnsServer.ServerDomain, 1, 28800, 7200, 604800, 600) });
+ }
+
+ private void ListBlockedZones(HttpListenerRequest request, JsonTextWriter jsonWriter)
+ {
+ string domain = request.QueryString["domain"];
+ if (domain == null)
+ domain = "";
+
+ string direction = request.QueryString["direction"];
+
+ string[] subZones;
+ DnsResourceRecord[] records;
+
+ while (true)
+ {
+ subZones = _dnsServer.BlockedZoneRoot.ListSubZones(domain);
+ records = _dnsServer.BlockedZoneRoot.GetAllRecords(domain, DnsResourceRecordType.ANY, false);
+
+ if (records.Length > 0)
+ break;
+
+ if (subZones.Length != 1)
+ break;
+
+ if (direction == "up")
+ {
+ if (domain == "")
+ break;
+
+ int i = domain.IndexOf('.');
+ if (i < 0)
+ domain = "";
+ else
+ domain = domain.Substring(i + 1);
+ }
+ else if (domain == "")
+ {
+ domain = subZones[0];
+ }
+ else
+ {
+ domain = subZones[0] + "." + domain;
+ }
+ }
+
+ Array.Sort(subZones);
+
+ jsonWriter.WritePropertyName("domain");
+ jsonWriter.WriteValue(domain);
+
+ jsonWriter.WritePropertyName("zones");
+ jsonWriter.WriteStartArray();
+
+ if (domain != "")
+ domain = "." + domain;
+
+ foreach (string subZone in subZones)
+ jsonWriter.WriteValue(subZone + domain);
+
+ jsonWriter.WriteEndArray();
+
+ WriteRecordsAsJson(records, jsonWriter, false);
+ }
+
+ private void ImportCustomBlockedZones(HttpListenerRequest request)
+ {
+ if (!request.ContentType.StartsWith("application/x-www-form-urlencoded"))
+ throw new WebServiceException("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(',');
+
+ foreach (string blockedZone in blockedZones)
+ {
+ BlockZone(blockedZone, _customBlockedZoneRoot, "custom");
+ BlockZone(blockedZone, _dnsServer.BlockedZoneRoot, "custom");
+ }
+
+ _log.Write(GetRequestRemoteEndPoint(request), "[" + GetSession(request).Username + "] Total " + blockedZones.Length + " zones were imported into custom blocked zone successfully.");
+ SaveCustomBlockedZoneFile();
+ return;
+ }
+ }
+
+ throw new WebServiceException("Parameter 'blockedZones' missing.");
+ }
+
+ private void ExportCustomBlockedZones(HttpListenerResponse response)
+ {
+ ICollection zoneInfoList = _customBlockedZoneRoot.ListAuthoritativeZones();
+
+ response.ContentType = "text/plain";
+ response.AddHeader("Content-Disposition", "attachment;filename=CustomBlockedZones.txt");
+
+ using (StreamWriter sW = new StreamWriter(new BufferedStream(response.OutputStream)))
+ {
+ foreach (ZoneInfo zoneInfo in zoneInfoList)
+ sW.WriteLine(zoneInfo.ZoneName);
+ }
+ }
+
+ private void FlushCustomBlockedZone(HttpListenerRequest request)
+ {
+ //delete custom blocked zones from dns blocked zone
+ foreach (ZoneInfo zone in _customBlockedZoneRoot.ListAuthoritativeZones())
+ _dnsServer.BlockedZoneRoot.DeleteZone(zone.ZoneName, false);
+
+ _customBlockedZoneRoot.Flush();
+
+ _log.Write(GetRequestRemoteEndPoint(request), "[" + GetSession(request).Username + "] Custom blocked zone was flushed.");
+
+ SaveCustomBlockedZoneFile();
+ _totalZonesBlocked = _dnsServer.BlockedZoneRoot.ListAuthoritativeZones().Count;
+ }
+
+ private void FlushBlockedZone(HttpListenerRequest request)
+ {
+ _dnsServer.BlockedZoneRoot.Flush();
+
+ //load custom blocked zone into dns block zone
+ foreach (ZoneInfo zone in _customBlockedZoneRoot.ListAuthoritativeZones())
+ BlockZone(zone.ZoneName, _dnsServer.BlockedZoneRoot, "custom");
+
+ _log.Write(GetRequestRemoteEndPoint(request), "[" + GetSession(request).Username + "] Blocked zone was flushed.");
+ _totalZonesBlocked = _dnsServer.BlockedZoneRoot.ListAuthoritativeZones().Count;
+ }
+
+ private void DeleteCustomBlockedZone(HttpListenerRequest request)
+ {
+ string domain = request.QueryString["domain"];
+ if (string.IsNullOrEmpty(domain))
+ throw new WebServiceException("Parameter 'domain' missing.");
+
+ bool customZoneDeleted = _customBlockedZoneRoot.DeleteZone(domain, false);
+ if (!customZoneDeleted)
+ throw new WebServiceException("Domain '" + domain + "' was not found in custom blocked zone. Try adding the domain into allowed zone instead to unblock it.");
+
+ _dnsServer.BlockedZoneRoot.DeleteZone(domain, false);
+
+ _log.Write(GetRequestRemoteEndPoint(request), "[" + GetSession(request).Username + "] Custom blocked zone was deleted: " + domain);
+
+ SaveCustomBlockedZoneFile();
+ _totalZonesBlocked--;
+ }
+
+ private void CustomBlockZone(HttpListenerRequest request)
+ {
+ string domain = request.QueryString["domain"];
+ if (string.IsNullOrEmpty(domain))
+ throw new WebServiceException("Parameter 'domain' missing.");
+
+ if (IPAddress.TryParse(domain, out IPAddress ipAddress))
+ domain = (new DnsQuestionRecord(ipAddress, DnsClass.IN)).Name;
+
+ BlockZone(domain, _customBlockedZoneRoot, "custom");
+ BlockZone(domain, _dnsServer.BlockedZoneRoot, "custom");
+
+ _log.Write(GetRequestRemoteEndPoint(request), "[" + GetSession(request).Username + "] Domain was added to custom block zone: " + domain);
+
+ SaveCustomBlockedZoneFile();
+ _totalZonesBlocked++;
+ }
+
+ private void BlockZone(string domain, Zone blockedZoneRoot, string blockListUrl)
+ {
+ blockedZoneRoot.SetRecords(new DnsResourceRecord[]
+ {
+ new DnsResourceRecord(domain, DnsResourceRecordType.SOA, DnsClass.IN, 60, new DnsSOARecord(_dnsServer.ServerDomain, "hostmaster." + _dnsServer.ServerDomain, 1, 28800, 7200, 604800, 600)),
+ new DnsResourceRecord(domain, DnsResourceRecordType.A, DnsClass.IN, 60, new DnsARecord(IPAddress.Any)),
+ new DnsResourceRecord(domain, DnsResourceRecordType.AAAA, DnsClass.IN, 60, new DnsAAAARecord(IPAddress.IPv6Any))
+ });
+
+ blockedZoneRoot.AddRecord(domain, DnsResourceRecordType.TXT, 60, new DnsTXTRecord("blockList=" + blockListUrl));
+ }
+
+ private void ListZones(JsonTextWriter jsonWriter)
+ {
+ ICollection zoneList = _dnsServer.AuthoritativeZoneRoot.ListAuthoritativeZones();
+
+ ZoneInfo[] zones = new ZoneInfo[zoneList.Count];
+ zoneList.CopyTo(zones, 0);
+
+ Array.Sort(zones);
+
+ jsonWriter.WritePropertyName("zones");
+ jsonWriter.WriteStartArray();
+
+ foreach (ZoneInfo zone in zones)
+ {
+ jsonWriter.WriteStartObject();
+
+ jsonWriter.WritePropertyName("zoneName");
+ jsonWriter.WriteValue(zone.ZoneName);
+
+ jsonWriter.WritePropertyName("disabled");
+ jsonWriter.WriteValue(zone.Disabled);
+
+ jsonWriter.WriteEndObject();
+ }
+
+ jsonWriter.WriteEndArray();
+ }
+
+ private void CreateZone(HttpListenerRequest request, JsonTextWriter jsonWriter)
+ {
+ string domain = request.QueryString["domain"];
+ if (string.IsNullOrEmpty(domain))
+ throw new WebServiceException("Parameter 'domain' missing.");
+
+ if (domain.Contains("*"))
+ throw new WebServiceException("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.EndsWith("."))
+ domain = domain.Substring(0, domain.Length - 1);
+
+ if (Zone.DomainEquals(domain, "resolver-associated-doh.arpa") || Zone.DomainEquals(domain, "resolver-addresses.arpa"))
+ throw new WebServiceException("Access was denied to manage special DNS Server zones.");
+
+ CreateZone(domain);
+ _log.Write(GetRequestRemoteEndPoint(request), "[" + GetSession(request).Username + "] Authoritative zone was created: " + domain);
+
+ SaveZoneFile(domain);
+
+ jsonWriter.WritePropertyName("domain");
+ jsonWriter.WriteValue(domain);
+ }
+
+ private void CreateZone(string domain)
+ {
+ _dnsServer.AuthoritativeZoneRoot.SetRecords(domain, DnsResourceRecordType.SOA, 14400, new DnsResourceRecordData[] { new DnsSOARecord(_dnsServer.ServerDomain, "hostmaster." + _dnsServer.ServerDomain, uint.Parse(DateTime.UtcNow.ToString("yyyyMMddHH")), 28800, 7200, 604800, 600) });
+ _dnsServer.AuthoritativeZoneRoot.SetRecords(domain, DnsResourceRecordType.NS, 14400, new DnsResourceRecordData[] { new DnsNSRecord(_dnsServer.ServerDomain) });
+ }
+
+ private void DeleteZone(HttpListenerRequest request)
+ {
+ string domain = request.QueryString["domain"];
+ if (string.IsNullOrEmpty(domain))
+ throw new WebServiceException("Parameter 'domain' missing.");
+
+ if (domain.EndsWith("."))
+ domain = domain.Substring(0, domain.Length - 1);
+
+ if (Zone.DomainEquals(domain, "resolver-associated-doh.arpa") || Zone.DomainEquals(domain, "resolver-addresses.arpa"))
+ throw new WebServiceException("Access was denied to manage special DNS Server zones.");
+
+ if (!_dnsServer.AuthoritativeZoneRoot.DeleteZone(domain, false))
+ throw new WebServiceException("Zone '" + domain + "' was not found.");
+
+ _log.Write(GetRequestRemoteEndPoint(request), "[" + GetSession(request).Username + "] Authoritative zone was deleted: " + domain);
+
+ DeleteZoneFile(domain);
+ }
+
+ private void EnableZone(HttpListenerRequest request)
+ {
+ string domain = request.QueryString["domain"];
+ if (string.IsNullOrEmpty(domain))
+ throw new WebServiceException("Parameter 'domain' missing.");
+
+ if (domain.EndsWith("."))
+ domain = domain.Substring(0, domain.Length - 1);
+
+ if (Zone.DomainEquals(domain, "resolver-associated-doh.arpa") || Zone.DomainEquals(domain, "resolver-addresses.arpa"))
+ throw new WebServiceException("Access was denied to manage special DNS Server zones.");
+
+ _dnsServer.AuthoritativeZoneRoot.EnableZone(domain);
+
+ _log.Write(GetRequestRemoteEndPoint(request), "[" + GetSession(request).Username + "] Authoritative zone was enabled: " + domain);
+
+ SaveZoneFile(domain);
+ }
+
+ private void DisableZone(HttpListenerRequest request)
+ {
+ string domain = request.QueryString["domain"];
+ if (string.IsNullOrEmpty(domain))
+ throw new WebServiceException("Parameter 'domain' missing.");
+
+ if (domain.EndsWith("."))
+ domain = domain.Substring(0, domain.Length - 1);
+
+ if (Zone.DomainEquals(domain, "resolver-associated-doh.arpa") || Zone.DomainEquals(domain, "resolver-addresses.arpa"))
+ throw new WebServiceException("Access was denied to manage special DNS Server zones.");
+
+ _dnsServer.AuthoritativeZoneRoot.DisableZone(domain);
+
+ _log.Write(GetRequestRemoteEndPoint(request), "[" + GetSession(request).Username + "] Authoritative zone was disabled: " + domain);
+
+ SaveZoneFile(domain);
+ }
+
+ private void AddRecord(HttpListenerRequest request)
+ {
+ string domain = request.QueryString["domain"];
+ if (string.IsNullOrEmpty(domain))
+ throw new WebServiceException("Parameter 'domain' missing.");
+
+ if (domain.EndsWith("."))
+ domain = domain.Substring(0, domain.Length - 1);
+
+ if (Zone.DomainEquals(domain, "resolver-associated-doh.arpa") || Zone.DomainEquals(domain, "resolver-addresses.arpa"))
+ throw new WebServiceException("Access was denied to manage special DNS Server zones.");
+
+ string strType = request.QueryString["type"];
+ if (string.IsNullOrEmpty(strType))
+ throw new WebServiceException("Parameter 'type' missing.");
+
+ DnsResourceRecordType type = (DnsResourceRecordType)Enum.Parse(typeof(DnsResourceRecordType), strType);
+
+ string value = request.QueryString["value"];
+ if (string.IsNullOrEmpty(value))
+ throw new WebServiceException("Parameter 'value' missing.");
+
+ uint ttl;
+ string strTtl = request.QueryString["ttl"];
+ if (string.IsNullOrEmpty(strTtl))
+ ttl = 3600;
+ else
+ ttl = uint.Parse(strTtl);
+
+ switch (type)
+ {
+ case DnsResourceRecordType.A:
+ _dnsServer.AuthoritativeZoneRoot.AddRecord(domain, type, ttl, new DnsARecord(IPAddress.Parse(value)));
+ break;
+
+ case DnsResourceRecordType.AAAA:
+ _dnsServer.AuthoritativeZoneRoot.AddRecord(domain, type, ttl, new DnsAAAARecord(IPAddress.Parse(value)));
+ break;
+
+ case DnsResourceRecordType.MX:
+ {
+ string preference = request.QueryString["preference"];
+ if (string.IsNullOrEmpty(preference))
+ throw new WebServiceException("Parameter 'preference' missing.");
+
+ _dnsServer.AuthoritativeZoneRoot.AddRecord(domain, type, ttl, new DnsMXRecord(ushort.Parse(preference), value));
+ }
+ break;
+
+ case DnsResourceRecordType.TXT:
+ _dnsServer.AuthoritativeZoneRoot.AddRecord(domain, type, ttl, new DnsTXTRecord(value));
+ break;
+
+ case DnsResourceRecordType.NS:
+ _dnsServer.AuthoritativeZoneRoot.AddRecord(domain, type, ttl, new DnsNSRecord(value));
+ break;
+
+ case DnsResourceRecordType.PTR:
+ _dnsServer.AuthoritativeZoneRoot.SetRecords(domain, type, ttl, new DnsResourceRecordData[] { new DnsPTRRecord(value) });
+ break;
+
+ case DnsResourceRecordType.CNAME:
+ _dnsServer.AuthoritativeZoneRoot.SetRecords(domain, type, ttl, new DnsResourceRecordData[] { new DnsCNAMERecord(value) });
+ break;
+
+ case DnsResourceRecordType.SRV:
+ {
+ string priority = request.QueryString["priority"];
+ if (string.IsNullOrEmpty(priority))
+ throw new WebServiceException("Parameter 'priority' missing.");
+
+ string weight = request.QueryString["weight"];
+ if (string.IsNullOrEmpty(weight))
+ throw new WebServiceException("Parameter 'weight' missing.");
+
+ string port = request.QueryString["port"];
+ if (string.IsNullOrEmpty(port))
+ throw new WebServiceException("Parameter 'port' missing.");
+
+ _dnsServer.AuthoritativeZoneRoot.AddRecord(domain, type, ttl, new DnsSRVRecord(ushort.Parse(priority), ushort.Parse(weight), ushort.Parse(port), value));
+ }
+ break;
+
+ default:
+ throw new WebServiceException("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 + ";}");
+
+ SaveZoneFile(domain);
+ }
+
+ private void GetRecords(HttpListenerRequest request, JsonTextWriter jsonWriter)
+ {
+ string domain = request.QueryString["domain"];
+ if (string.IsNullOrEmpty(domain))
+ throw new WebServiceException("Parameter 'domain' missing.");
+
+ if (domain.EndsWith("."))
+ domain = domain.Substring(0, domain.Length - 1);
+
+ DnsResourceRecord[] records = _dnsServer.AuthoritativeZoneRoot.GetAllRecords(domain);
+ if (records.Length == 0)
+ throw new WebServiceException("Zone '" + domain + "' was not found.");
+
+ WriteRecordsAsJson(records, jsonWriter, true);
+ }
+
+ private void WriteRecordsAsJson(DnsResourceRecord[] records, JsonTextWriter jsonWriter, bool authoritativeZoneRecords)
+ {
+ if (records == null)
+ {
+ jsonWriter.WritePropertyName("records");
+ jsonWriter.WriteStartArray();
+ jsonWriter.WriteEndArray();
+
+ return;
+ }
+
+ Array.Sort(records);
+
+ Dictionary>> groupedByDomainRecords = DnsResourceRecord.GroupRecords(records);
+
+ jsonWriter.WritePropertyName("records");
+ jsonWriter.WriteStartArray();
+
+ foreach (KeyValuePair>> groupedByTypeRecords in groupedByDomainRecords)
+ {
+ foreach (KeyValuePair> groupedRecords in groupedByTypeRecords.Value)
+ {
+ foreach (DnsResourceRecord resourceRecord in groupedRecords.Value)
+ {
+ jsonWriter.WriteStartObject();
+
+ if (authoritativeZoneRecords)
+ {
+ DnsResourceRecordInfo rrInfo = resourceRecord.Tag as DnsResourceRecordInfo;
+ jsonWriter.WritePropertyName("disabled");
+ jsonWriter.WriteValue((rrInfo != null) && rrInfo.Disabled);
+ }
+
+ jsonWriter.WritePropertyName("name");
+ jsonWriter.WriteValue(resourceRecord.Name);
+
+ jsonWriter.WritePropertyName("type");
+ jsonWriter.WriteValue(resourceRecord.Type.ToString());
+
+ jsonWriter.WritePropertyName("ttl");
+ if (authoritativeZoneRecords)
+ jsonWriter.WriteValue(resourceRecord.TtlValue);
+ else
+ jsonWriter.WriteValue(resourceRecord.TTL);
+
+ jsonWriter.WritePropertyName("rData");
+ jsonWriter.WriteStartObject();
+
+ switch (resourceRecord.Type)
+ {
+ case DnsResourceRecordType.A:
+ {
+ DnsARecord rdata = (resourceRecord.RDATA as DnsARecord);
+ if (rdata != null)
+ {
+ jsonWriter.WritePropertyName("value");
+ jsonWriter.WriteValue(rdata.IPAddress);
+ }
+ }
+ break;
+
+ case DnsResourceRecordType.AAAA:
+ {
+ DnsAAAARecord rdata = (resourceRecord.RDATA as DnsAAAARecord);
+ if (rdata != null)
+ {
+ jsonWriter.WritePropertyName("value");
+ jsonWriter.WriteValue(rdata.IPAddress);
+ }
+ }
+ break;
+
+ case DnsResourceRecordType.SOA:
+ {
+ DnsSOARecord rdata = resourceRecord.RDATA as DnsSOARecord;
+ if (rdata != null)
+ {
+ jsonWriter.WritePropertyName("masterNameServer");
+ jsonWriter.WriteValue(rdata.MasterNameServer);
+
+ 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);
+ }
+ }
+ break;
+
+ case DnsResourceRecordType.PTR:
+ {
+ DnsPTRRecord rdata = resourceRecord.RDATA as DnsPTRRecord;
+ if (rdata != null)
+ {
+ jsonWriter.WritePropertyName("value");
+ jsonWriter.WriteValue(rdata.PTRDomainName);
+ }
+ }
+ break;
+
+ case DnsResourceRecordType.MX:
+ {
+ DnsMXRecord rdata = resourceRecord.RDATA as DnsMXRecord;
+ if (rdata != null)
+ {
+ jsonWriter.WritePropertyName("preference");
+ jsonWriter.WriteValue(rdata.Preference);
+
+ jsonWriter.WritePropertyName("value");
+ jsonWriter.WriteValue(rdata.Exchange);
+ }
+ }
+ break;
+
+ case DnsResourceRecordType.TXT:
+ {
+ DnsTXTRecord rdata = resourceRecord.RDATA as DnsTXTRecord;
+ if (rdata != null)
+ {
+ jsonWriter.WritePropertyName("value");
+ jsonWriter.WriteValue(rdata.TXTData);
+ }
+ }
+ break;
+
+ case DnsResourceRecordType.NS:
+ {
+ DnsNSRecord rdata = resourceRecord.RDATA as DnsNSRecord;
+ if (rdata != null)
+ {
+ jsonWriter.WritePropertyName("value");
+ jsonWriter.WriteValue(rdata.NSDomainName);
+ }
+ }
+ break;
+
+ case DnsResourceRecordType.CNAME:
+ {
+ DnsCNAMERecord rdata = resourceRecord.RDATA as DnsCNAMERecord;
+ if (rdata != null)
+ {
+ jsonWriter.WritePropertyName("value");
+ jsonWriter.WriteValue(rdata.CNAMEDomainName);
+ }
+ }
+ break;
+
+ case DnsResourceRecordType.SRV:
+ {
+ DnsSRVRecord rdata = resourceRecord.RDATA as DnsSRVRecord;
+ if (rdata != null)
+ {
+ 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);
+ }
+ }
+ break;
+
+ default:
+ {
+ jsonWriter.WritePropertyName("value");
+
+ using (MemoryStream mS = new MemoryStream())
+ {
+ resourceRecord.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 WebServiceException("Parameter 'domain' missing.");
+
+ if (domain.EndsWith("."))
+ domain = domain.Substring(0, domain.Length - 1);
+
+ if (Zone.DomainEquals(domain, "resolver-associated-doh.arpa") || Zone.DomainEquals(domain, "resolver-addresses.arpa"))
+ throw new WebServiceException("Access was denied to manage special DNS Server zones.");
+
+ string strType = request.QueryString["type"];
+ if (string.IsNullOrEmpty(strType))
+ throw new WebServiceException("Parameter 'type' missing.");
+
+ DnsResourceRecordType type = (DnsResourceRecordType)Enum.Parse(typeof(DnsResourceRecordType), strType);
+
+ string value = request.QueryString["value"];
+ if (string.IsNullOrEmpty(value))
+ throw new WebServiceException("Parameter 'value' missing.");
+
+ if (!_dnsServer.AuthoritativeZoneRoot.ZoneExists(domain))
+ throw new WebServiceException("Zone '" + domain + "' was not found.");
+
+ switch (type)
+ {
+ case DnsResourceRecordType.A:
+ _dnsServer.AuthoritativeZoneRoot.DeleteRecord(domain, type, new DnsARecord(IPAddress.Parse(value)));
+ break;
+
+ case DnsResourceRecordType.AAAA:
+ _dnsServer.AuthoritativeZoneRoot.DeleteRecord(domain, type, new DnsAAAARecord(IPAddress.Parse(value)));
+ break;
+
+ case DnsResourceRecordType.MX:
+ _dnsServer.AuthoritativeZoneRoot.DeleteRecord(domain, type, new DnsMXRecord(0, value));
+ break;
+
+ case DnsResourceRecordType.TXT:
+ _dnsServer.AuthoritativeZoneRoot.DeleteRecord(domain, type, new DnsTXTRecord(value));
+ break;
+
+ case DnsResourceRecordType.NS:
+ _dnsServer.AuthoritativeZoneRoot.DeleteRecord(domain, type, new DnsNSRecord(value));
+ break;
+
+ case DnsResourceRecordType.CNAME:
+ case DnsResourceRecordType.PTR:
+ _dnsServer.AuthoritativeZoneRoot.DeleteRecords(domain, type);
+ break;
+
+ case DnsResourceRecordType.SRV:
+ {
+ string port = request.QueryString["port"];
+ if (string.IsNullOrEmpty(port))
+ throw new WebServiceException("Parameter 'port' missing.");
+
+ _dnsServer.AuthoritativeZoneRoot.DeleteRecord(domain, type, new DnsSRVRecord(0, 0, ushort.Parse(port), value));
+ }
+ break;
+
+ default:
+ throw new WebServiceException("Type not supported for DeleteRecord().");
+ }
+
+ _log.Write(GetRequestRemoteEndPoint(request), "[" + GetSession(request).Username + "] Record was deleted from authoritative zone {domain: " + domain + "; type: " + type + "; value: " + value + ";}");
+
+ SaveZoneFile(domain);
+ }
+
+ private void UpdateRecord(HttpListenerRequest request)
+ {
+ string strType = request.QueryString["type"];
+ if (string.IsNullOrEmpty(strType))
+ throw new WebServiceException("Parameter 'type' missing.");
+
+ DnsResourceRecordType type = (DnsResourceRecordType)Enum.Parse(typeof(DnsResourceRecordType), strType);
+
+ string domain = request.QueryString["domain"];
+ if (string.IsNullOrEmpty(domain))
+ throw new WebServiceException("Parameter 'domain' missing.");
+
+ if (domain.EndsWith("."))
+ domain = domain.Substring(0, domain.Length - 1);
+
+ if (Zone.DomainEquals(domain, "resolver-associated-doh.arpa") || Zone.DomainEquals(domain, "resolver-addresses.arpa"))
+ throw new WebServiceException("Access was denied to manage special DNS Server zones.");
+
+ string oldDomain = request.QueryString["oldDomain"];
+ if (string.IsNullOrEmpty(oldDomain))
+ oldDomain = domain;
+
+ if (oldDomain.EndsWith("."))
+ oldDomain = oldDomain.Substring(0, oldDomain.Length - 1);
+
+ string value = request.QueryString["value"];
+ string oldValue = request.QueryString["oldValue"];
+
+ uint ttl;
+ string strTtl = request.QueryString["ttl"];
+ if (string.IsNullOrEmpty(strTtl))
+ ttl = 3600;
+ else
+ ttl = uint.Parse(strTtl);
+
+ bool disable = false;
+ string strDisable = request.QueryString["disable"];
+ if (!string.IsNullOrEmpty(strDisable))
+ disable = bool.Parse(strDisable);
+
+ switch (type)
+ {
+ case DnsResourceRecordType.A:
+ _dnsServer.AuthoritativeZoneRoot.UpdateRecord(new DnsResourceRecord(oldDomain, type, DnsClass.IN, 0, new DnsARecord(IPAddress.Parse(oldValue))), new DnsResourceRecord(domain, type, DnsClass.IN, ttl, new DnsARecord(IPAddress.Parse(value))) { Tag = new DnsResourceRecordInfo(disable) });
+ break;
+
+ case DnsResourceRecordType.AAAA:
+ _dnsServer.AuthoritativeZoneRoot.UpdateRecord(new DnsResourceRecord(oldDomain, type, DnsClass.IN, 0, new DnsAAAARecord(IPAddress.Parse(oldValue))), new DnsResourceRecord(domain, type, DnsClass.IN, ttl, new DnsAAAARecord(IPAddress.Parse(value))) { Tag = new DnsResourceRecordInfo(disable) });
+ break;
+
+ case DnsResourceRecordType.MX:
+ string preference = request.QueryString["preference"];
+ if (string.IsNullOrEmpty(preference))
+ throw new WebServiceException("Parameter 'preference' missing.");
+
+ _dnsServer.AuthoritativeZoneRoot.UpdateRecord(new DnsResourceRecord(oldDomain, type, DnsClass.IN, 0, new DnsMXRecord(0, oldValue)), new DnsResourceRecord(domain, type, DnsClass.IN, ttl, new DnsMXRecord(ushort.Parse(preference), value)) { Tag = new DnsResourceRecordInfo(disable) });
+ break;
+
+ case DnsResourceRecordType.TXT:
+ _dnsServer.AuthoritativeZoneRoot.UpdateRecord(new DnsResourceRecord(oldDomain, type, DnsClass.IN, 0, new DnsTXTRecord(oldValue)), new DnsResourceRecord(domain, type, DnsClass.IN, ttl, new DnsTXTRecord(value)) { Tag = new DnsResourceRecordInfo(disable) });
+ break;
+
+ case DnsResourceRecordType.NS:
+ _dnsServer.AuthoritativeZoneRoot.UpdateRecord(new DnsResourceRecord(oldDomain, type, DnsClass.IN, 0, new DnsNSRecord(oldValue)), new DnsResourceRecord(domain, type, DnsClass.IN, ttl, new DnsNSRecord(value)) { Tag = new DnsResourceRecordInfo(disable) });
+ break;
+
+ case DnsResourceRecordType.SOA:
+ {
+ string masterNameServer = request.QueryString["masterNameServer"];
+ if (string.IsNullOrEmpty(masterNameServer))
+ throw new WebServiceException("Parameter 'masterNameServer' missing.");
+
+ string responsiblePerson = request.QueryString["responsiblePerson"];
+ if (string.IsNullOrEmpty(responsiblePerson))
+ throw new WebServiceException("Parameter 'responsiblePerson' missing.");
+
+ string serial = request.QueryString["serial"];
+ if (string.IsNullOrEmpty(serial))
+ throw new WebServiceException("Parameter 'serial' missing.");
+
+ string refresh = request.QueryString["refresh"];
+ if (string.IsNullOrEmpty(refresh))
+ throw new WebServiceException("Parameter 'refresh' missing.");
+
+ string retry = request.QueryString["retry"];
+ if (string.IsNullOrEmpty(retry))
+ throw new WebServiceException("Parameter 'retry' missing.");
+
+ string expire = request.QueryString["expire"];
+ if (string.IsNullOrEmpty(expire))
+ throw new WebServiceException("Parameter 'expire' missing.");
+
+ string minimum = request.QueryString["minimum"];
+ if (string.IsNullOrEmpty(minimum))
+ throw new WebServiceException("Parameter 'minimum' missing.");
+
+ _dnsServer.AuthoritativeZoneRoot.SetRecords(domain, type, ttl, new DnsResourceRecordData[] { new DnsSOARecord(masterNameServer, responsiblePerson, uint.Parse(serial), uint.Parse(refresh), uint.Parse(retry), uint.Parse(expire), uint.Parse(minimum)) });
+ }
+ break;
+
+ case DnsResourceRecordType.PTR:
+ _dnsServer.AuthoritativeZoneRoot.UpdateRecord(new DnsResourceRecord(oldDomain, type, DnsClass.IN, 0, new DnsPTRRecord(oldValue)), new DnsResourceRecord(domain, type, DnsClass.IN, ttl, new DnsPTRRecord(value)) { Tag = new DnsResourceRecordInfo(disable) });
+ break;
+
+ case DnsResourceRecordType.CNAME:
+ _dnsServer.AuthoritativeZoneRoot.UpdateRecord(new DnsResourceRecord(oldDomain, type, DnsClass.IN, 0, new DnsCNAMERecord(oldValue)), new DnsResourceRecord(domain, type, DnsClass.IN, ttl, new DnsCNAMERecord(value)) { Tag = new DnsResourceRecordInfo(disable) });
+ break;
+
+ case DnsResourceRecordType.SRV:
+ {
+ string oldPort = request.QueryString["oldPort"];
+ if (string.IsNullOrEmpty(oldPort))
+ throw new WebServiceException("Parameter 'oldPort' missing.");
+
+ string priority = request.QueryString["priority"];
+ if (string.IsNullOrEmpty(priority))
+ throw new WebServiceException("Parameter 'priority' missing.");
+
+ string weight = request.QueryString["weight"];
+ if (string.IsNullOrEmpty(weight))
+ throw new WebServiceException("Parameter 'weight' missing.");
+
+ string port = request.QueryString["port"];
+ if (string.IsNullOrEmpty(port))
+ throw new WebServiceException("Parameter 'port' missing.");
+
+ DnsResourceRecord oldRecord = new DnsResourceRecord(oldDomain, type, DnsClass.IN, 0, new DnsSRVRecord(0, 0, ushort.Parse(oldPort), oldValue));
+ DnsResourceRecord newRecord = new DnsResourceRecord(domain, type, DnsClass.IN, ttl, new DnsSRVRecord(ushort.Parse(priority), ushort.Parse(weight), ushort.Parse(port), value)) { Tag = new DnsResourceRecordInfo(disable) };
+
+ _dnsServer.AuthoritativeZoneRoot.UpdateRecord(oldRecord, newRecord);
+ }
+ break;
+
+ default:
+ throw new WebServiceException("Type not supported for UpdateRecords().");
+ }
+
+ _log.Write(GetRequestRemoteEndPoint(request), "[" + GetSession(request).Username + "] Record was updated for authoritative zone {oldDomain: " + oldDomain + "; domain: " + domain + "; type: " + type + "; oldValue: " + oldValue + "; value: " + value + "; ttl: " + ttl + "; disabled: " + disable + ";}");
+
+ SaveZoneFile(domain);
+ }
+
+ private void ResolveQuery(HttpListenerRequest request, JsonTextWriter jsonWriter)
+ {
+ string server = request.QueryString["server"];
+ if (string.IsNullOrEmpty(server))
+ throw new WebServiceException("Parameter 'server' missing.");
+
+ string domain = request.QueryString["domain"];
+ if (string.IsNullOrEmpty(domain))
+ throw new WebServiceException("Parameter 'domain' missing.");
+
+ if (domain.EndsWith("."))
+ domain = domain.Substring(0, domain.Length - 1);
+
+ string strType = request.QueryString["type"];
+ if (string.IsNullOrEmpty(strType))
+ throw new WebServiceException("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;
+ DnsTransportProtocol protocol = (DnsTransportProtocol)Enum.Parse(typeof(DnsTransportProtocol), strProtocol, true);
+ const int RETRIES = 1;
+ const int TIMEOUT = 10000;
+
+ DnsDatagram dnsResponse;
+
+ if (server == "recursive-resolver")
+ {
+ DnsQuestionRecord question;
+
+ if (type == DnsResourceRecordType.PTR)
+ question = new DnsQuestionRecord(IPAddress.Parse(domain), DnsClass.IN);
+ else
+ question = new DnsQuestionRecord(domain, type, DnsClass.IN);
+
+ dnsResponse = DnsClient.RecursiveResolve(question, null, null, proxy, preferIPv6, RETRIES, TIMEOUT);
+ }
+ else
+ {
+ NameServerAddress nameServer;
+
+ if (server == "this-server")
+ {
+ nameServer = new NameServerAddress(_dnsServer.ServerDomain, IPAddress.Parse("127.0.0.1"));
+ proxy = null; //no proxy required for this server
+ }
+ else
+ {
+ nameServer = new NameServerAddress(server);
+
+ if (nameServer.IPEndPoint == null)
+ {
+ if (proxy == null)
+ {
+ if (_dnsServer.AllowRecursion)
+ nameServer.ResolveIPAddress(new NameServerAddress[] { new NameServerAddress(IPAddress.Loopback) }, proxy, preferIPv6, RETRIES, TIMEOUT);
+ else
+ nameServer.RecursiveResolveIPAddress(_dnsServer.Cache, proxy, preferIPv6, RETRIES, TIMEOUT);
+ }
+ }
+ else if (protocol != DnsTransportProtocol.Tls)
+ {
+ try
+ {
+ if (_dnsServer.AllowRecursion)
+ nameServer.ResolveDomainName(new NameServerAddress[] { new NameServerAddress(IPAddress.Loopback) }, proxy, preferIPv6, RETRIES, TIMEOUT);
+ else
+ nameServer.RecursiveResolveDomainName(_dnsServer.Cache, proxy, preferIPv6, RETRIES, TIMEOUT);
+ }
+ catch
+ { }
+ }
+ }
+
+ dnsResponse = (new DnsClient(nameServer) { Proxy = proxy, PreferIPv6 = preferIPv6, Protocol = protocol, Retries = RETRIES, Timeout = TIMEOUT }).Resolve(domain, type);
+ }
+
+ if (importRecords)
+ {
+ List recordsToSet = new List();
+ bool containsSOARecord = false;
+
+ foreach (DnsResourceRecord record in dnsResponse.Answer)
+ {
+ if (record.Name.Equals(domain, StringComparison.OrdinalIgnoreCase))
+ {
+ recordsToSet.Add(record);
+
+ if (record.Type == DnsResourceRecordType.SOA)
+ containsSOARecord = true;
+ }
+ }
+
+ if (!containsSOARecord)
+ {
+ bool SOARecordExists = false;
+
+ foreach (ZoneInfo zone in _dnsServer.AuthoritativeZoneRoot.ListAuthoritativeZones())
+ {
+ if (domain.EndsWith(zone.ZoneName, StringComparison.OrdinalIgnoreCase))
+ {
+ SOARecordExists = true;
+ break;
+ }
+ }
+
+ if (!SOARecordExists)
+ _dnsServer.AuthoritativeZoneRoot.SetRecords(domain, DnsResourceRecordType.SOA, 14400, new DnsResourceRecordData[] { new DnsSOARecord(_dnsServer.ServerDomain, "hostmaster." + _dnsServer.ServerDomain, uint.Parse(DateTime.UtcNow.ToString("yyyyMMddHH")), 28800, 7200, 604800, 600) });
+ }
+
+ _dnsServer.AuthoritativeZoneRoot.SetRecords(recordsToSet);
+
+ _log.Write(GetRequestRemoteEndPoint(request), "[" + GetSession(request).Username + "] DNS Client imported record(s) for authoritative zone {server: " + server + "; domain: " + domain + "; type: " + type + ";}");
+
+ SaveZoneFile(domain);
+ }
+
+ jsonWriter.WritePropertyName("result");
+ jsonWriter.WriteRawValue(JsonConvert.SerializeObject(dnsResponse, new StringEnumConverter()));
+ }
+
+ private void ListLogs(JsonTextWriter jsonWriter)
+ {
+ string[] logFiles = Directory.GetFiles(_log.LogFolder, "*.log");
+
+ 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 WebServiceException("Parameter 'log' missing.");
+
+ string logFile = Path.Combine(_log.LogFolder, log + ".log");
+
+ if (_log.CurrentLogFile.Equals(logFile, StringComparison.OrdinalIgnoreCase))
+ _log.DeleteCurrentLogFile();
+ else
+ File.Delete(logFile);
+
+ _log.Write(GetRequestRemoteEndPoint(request), "[" + GetSession(request).Username + "] Log file was deleted: " + log);
+ }
+
+ private void ListDhcpLeases(JsonTextWriter jsonWriter)
+ {
+ ICollection scopes = _dhcpServer.Scopes;
+
+ jsonWriter.WritePropertyName("leases");
+ jsonWriter.WriteStartArray();
+
+ foreach (Scope scope in scopes)
+ {
+ foreach (Lease lease in scope.Leases)
+ {
+ jsonWriter.WriteStartObject();
+
+ jsonWriter.WritePropertyName("scope");
+ jsonWriter.WriteValue(scope.Name);
+
+ 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.ToString());
+
+ jsonWriter.WritePropertyName("leaseExpires");
+ jsonWriter.WriteValue(lease.LeaseExpires.ToString());
+
+ jsonWriter.WriteEndObject();
+ }
+ }
+
+ jsonWriter.WriteEndArray();
+ }
+
+ private void ListDhcpScopes(JsonTextWriter jsonWriter)
+ {
+ ICollection scopes = _dhcpServer.Scopes;
+
+ jsonWriter.WritePropertyName("scopes");
+ jsonWriter.WriteStartArray();
+
+ foreach (Scope scope in scopes)
+ {
+ 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());
+
+ 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 WebServiceException("Parameter 'name' missing.");
+
+ Scope scope = _dhcpServer.GetScope(scopeName);
+ if (scope == null)
+ throw new WebServiceException("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("leaseTime");
+ jsonWriter.WriteValue(scope.LeaseTime);
+
+ 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.RouterAddress != null)
+ {
+ jsonWriter.WritePropertyName("routerAddress");
+ jsonWriter.WriteValue(scope.RouterAddress.ToString());
+ }
+
+ 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.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("hardwareAddress");
+ jsonWriter.WriteValue(BitConverter.ToString(reservedLease.HardwareAddress));
+
+ jsonWriter.WritePropertyName("address");
+ jsonWriter.WriteValue(reservedLease.Address.ToString());
+
+ jsonWriter.WriteEndObject();
+ }
+
+ jsonWriter.WriteEndArray();
+ }
+
+ jsonWriter.WritePropertyName("allowOnlyReservedLeases");
+ jsonWriter.WriteValue(scope.AllowOnlyReservedLeases);
+ }
+
+ private void SetDhcpScope(HttpListenerRequest request)
+ {
+ string scopeName = request.QueryString["name"];
+ if (string.IsNullOrEmpty(scopeName))
+ throw new WebServiceException("Parameter 'name' missing.");
+
+ string newName = request.QueryString["newName"];
+ if (!string.IsNullOrEmpty(newName))
+ _dhcpServer.RenameScope(scopeName, newName);
+
+ string strStartingAddress = request.QueryString["startingAddress"];
+ if (string.IsNullOrEmpty(strStartingAddress))
+ throw new WebServiceException("Parameter 'startingAddress' missing.");
+
+ string strEndingAddress = request.QueryString["endingAddress"];
+ if (string.IsNullOrEmpty(strStartingAddress))
+ throw new WebServiceException("Parameter 'endingAddress' missing.");
+
+ string strSubnetMask = request.QueryString["subnetMask"];
+ if (string.IsNullOrEmpty(strStartingAddress))
+ throw new WebServiceException("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;
+ scope.ChangeNetwork(IPAddress.Parse(strStartingAddress), IPAddress.Parse(strEndingAddress), IPAddress.Parse(strSubnetMask));
+ }
+
+ string strLeaseTime = request.QueryString["leaseTime"];
+ if (!string.IsNullOrEmpty(strLeaseTime))
+ scope.LeaseTime = uint.Parse(strLeaseTime);
+
+ 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 == "" ? null : strDomainName;
+
+ string strDnsTtl = request.QueryString["dnsTtl"];
+ if (!string.IsNullOrEmpty(strDnsTtl))
+ scope.DnsTtl = uint.Parse(strDnsTtl);
+
+ string strRouterAddress = request.QueryString["routerAddress"];
+ if (strRouterAddress != null)
+ scope.RouterAddress = strRouterAddress == "" ? null : IPAddress.Parse(strRouterAddress);
+
+ string strDnsServers = request.QueryString["dnsServers"];
+ if (strDnsServers != null)
+ {
+ if (strDnsServers == "")
+ {
+ 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 == "")
+ {
+ 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 == "")
+ {
+ 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 == "")
+ {
+ scope.StaticRoutes = null;
+ }
+ else
+ {
+ string[] strStaticRoutesParts = strStaticRoutes.Split(new char[] { ',' }, StringSplitOptions.RemoveEmptyEntries);
+ ClasslessStaticRouteOption.Route[] staticRoutes = new ClasslessStaticRouteOption.Route[strStaticRoutesParts.Length];
+
+ for (int i = 0; i < strStaticRoutesParts.Length; i++)
+ {
+ string[] routeParts = strStaticRoutesParts[i].Split(';');
+
+ staticRoutes[i] = new ClasslessStaticRouteOption.Route(IPAddress.Parse(routeParts[0]), IPAddress.Parse(routeParts[1]), IPAddress.Parse(routeParts[2]));
+ }
+
+ scope.StaticRoutes = staticRoutes;
+ }
+ }
+
+ string strExclusions = request.QueryString["exclusions"];
+ if (strExclusions != null)
+ {
+ if (strExclusions == "")
+ {
+ scope.Exclusions = null;
+ }
+ else
+ {
+ string[] strExclusionsParts = strExclusions.Split(new char[] { ',' }, StringSplitOptions.RemoveEmptyEntries);
+ Exclusion[] exclusions = new Exclusion[strExclusionsParts.Length];
+
+ for (int i = 0; i < strExclusionsParts.Length; i++)
+ {
+ string[] rangeParts = strExclusionsParts[i].Split('-');
+
+ exclusions[i] = new Exclusion(IPAddress.Parse(rangeParts[0]), IPAddress.Parse(rangeParts[1]));
+ }
+
+ scope.Exclusions = exclusions;
+ }
+ }
+
+ string strReservedLeases = request.QueryString["reservedLeases"];
+ if (strReservedLeases != null)
+ {
+ if (strReservedLeases == "")
+ {
+ scope.ReservedLeases = null;
+ }
+ else
+ {
+ string[] strReservedLeaseParts = strReservedLeases.Split(new char[] { ',' }, StringSplitOptions.RemoveEmptyEntries);
+ Lease[] reservedLeases = new Lease[strReservedLeaseParts.Length];
+
+ for (int i = 0; i < strReservedLeaseParts.Length; i++)
+ {
+ string[] leaseParts = strReservedLeaseParts[i].Split(';');
+
+ reservedLeases[i] = new Lease(leaseParts[0], IPAddress.Parse(leaseParts[1]));
+ }
+
+ scope.ReservedLeases = reservedLeases;
+ }
+ }
+
+ string strAllowOnlyReservedLeases = request.QueryString["allowOnlyReservedLeases"];
+ if (!string.IsNullOrEmpty(strAllowOnlyReservedLeases))
+ scope.AllowOnlyReservedLeases = bool.Parse(strAllowOnlyReservedLeases);
+
+ if (!scopeExists)
+ _dhcpServer.AddScope(scope);
+ }
+
+ private void EnableDhcpScope(HttpListenerRequest request)
+ {
+ string scopeName = request.QueryString["name"];
+ if (string.IsNullOrEmpty(scopeName))
+ throw new WebServiceException("Parameter 'name' missing.");
+
+ Scope scope = _dhcpServer.GetScope(scopeName);
+ if (scope == null)
+ throw new WebServiceException("DHCP scope was not found: " + scopeName);
+
+ _dhcpServer.EnableScope(scopeName);
+ }
+
+ private void DisableDhcpScope(HttpListenerRequest request)
+ {
+ string scopeName = request.QueryString["name"];
+ if (string.IsNullOrEmpty(scopeName))
+ throw new WebServiceException("Parameter 'name' missing.");
+
+ Scope scope = _dhcpServer.GetScope(scopeName);
+ if (scope == null)
+ throw new WebServiceException("DHCP scope was not found: " + scopeName);
+
+ _dhcpServer.DisableScope(scopeName);
+ }
+
+ private void DeleteDhcpScope(HttpListenerRequest request)
+ {
+ string scopeName = request.QueryString["name"];
+ if (string.IsNullOrEmpty(scopeName))
+ throw new WebServiceException("Parameter 'name' missing.");
+
+ _dhcpServer.DeleteScope(scopeName);
+ }
+
+ private void SetCredentials(string username, string password)
+ {
+ username = username.ToLower();
+ string passwordHash = GetPasswordHash(username, password);
+
+ _credentials.AddOrUpdate(username, passwordHash, delegate (string key, string oldValue)
+ {
+ return passwordHash;
+ });
+ }
+
+ private void LoadCredentials(string username, string passwordHash)
+ {
+ username = username.ToLower();
+
+ _credentials.AddOrUpdate(username, passwordHash, delegate (string key, string oldValue)
+ {
+ return 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();
+ }
+ }
+
+ private void LoadZoneFiles()
+ {
+ string[] zoneFiles = Directory.GetFiles(_configFolder, "*.zone");
+
+ if (zoneFiles.Length == 0)
+ {
+ {
+ CreateZone("localhost");
+ _dnsServer.AuthoritativeZoneRoot.SetRecords("localhost", DnsResourceRecordType.A, 3600, new DnsResourceRecordData[] { new DnsARecord(IPAddress.Loopback) });
+ _dnsServer.AuthoritativeZoneRoot.SetRecords("localhost", DnsResourceRecordType.AAAA, 3600, new DnsResourceRecordData[] { new DnsAAAARecord(IPAddress.IPv6Loopback) });
+
+ SaveZoneFile("localhost");
+ }
+
+ {
+ string prtDomain = new DnsQuestionRecord(IPAddress.Loopback, DnsClass.IN).Name;
+
+ CreateZone(prtDomain);
+ _dnsServer.AuthoritativeZoneRoot.SetRecords(prtDomain, DnsResourceRecordType.PTR, 3600, new DnsResourceRecordData[] { new DnsPTRRecord("localhost") });
+
+ SaveZoneFile(prtDomain);
+ }
+
+ {
+ string prtDomain = new DnsQuestionRecord(IPAddress.IPv6Loopback, DnsClass.IN).Name;
+
+ CreateZone(prtDomain);
+ _dnsServer.AuthoritativeZoneRoot.SetRecords(prtDomain, DnsResourceRecordType.PTR, 3600, new DnsResourceRecordData[] { new DnsPTRRecord("localhost") });
+
+ SaveZoneFile(prtDomain);
+ }
+ }
+ else
+ {
+ foreach (string zoneFile in zoneFiles)
+ {
+ try
+ {
+ LoadZoneFile(zoneFile);
+ }
+ catch (Exception ex)
+ {
+ _log.Write("DNS Server failed to load zone file: " + zoneFile + "\r\n" + ex.ToString());
+ }
+ }
+ }
+ }
+
+ private void LoadZoneFile(string zoneFile)
+ {
+ using (FileStream fS = new FileStream(zoneFile, FileMode.Open, FileAccess.Read))
+ {
+ BinaryReader bR = new BinaryReader(fS);
+
+ if (Encoding.ASCII.GetString(bR.ReadBytes(2)) != "DZ")
+ throw new InvalidDataException("DnsServer zone file format is invalid.");
+
+ switch (bR.ReadByte())
+ {
+ case 1:
+ fS.Position = 0;
+ LoadZoneFileV1(fS);
+ break;
+
+ case 2:
+ {
+ int count = bR.ReadInt32();
+ DnsResourceRecord[] records = new DnsResourceRecord[count];
+
+ for (int i = 0; i < count; i++)
+ records[i] = new DnsResourceRecord(fS);
+
+ _dnsServer.AuthoritativeZoneRoot.SetRecords(records);
+ }
+ break;
+
+ case 3:
+ {
+ bool zoneDisabled = bR.ReadBoolean();
+ int count = bR.ReadInt32();
+
+ if (count > 0)
+ {
+ DnsResourceRecord[] records = new DnsResourceRecord[count];
+
+ for (int i = 0; i < count; i++)
+ {
+ records[i] = new DnsResourceRecord(fS);
+ records[i].Tag = new DnsResourceRecordInfo(new BinaryReader(fS));
+ }
+
+ _dnsServer.AuthoritativeZoneRoot.SetRecords(records);
+
+ if (zoneDisabled)
+ _dnsServer.AuthoritativeZoneRoot.DisableZone(records[0].Name);
+ }
+ }
+ break;
+
+ default:
+ throw new InvalidDataException("DNS Zone file version not supported.");
+ }
+ }
+
+ _log.Write("DNS Server successfully loaded zone file: " + zoneFile);
+ }
+
+ private void LoadZoneFileV1(Stream s)
+ {
+ BincodingDecoder decoder = new BincodingDecoder(s, "DZ");
+
+ switch (decoder.Version)
+ {
+ case 1:
+ ICollection entries = decoder.DecodeNext().GetList();
+ DnsResourceRecord[] records = new DnsResourceRecord[entries.Count];
+
+ int i = 0;
+ foreach (Bincoding entry in entries)
+ records[i++] = new DnsResourceRecord(entry.GetValueStream());
+
+ _dnsServer.AuthoritativeZoneRoot.SetRecords(records);
+ break;
+
+ default:
+ throw new IOException("DNS Zone file version not supported: " + decoder.Version);
+ }
+ }
+
+ private void SaveZoneFile(string domain)
+ {
+ domain = domain.ToLower();
+ DnsResourceRecord[] records = _dnsServer.AuthoritativeZoneRoot.GetAllRecords(domain, DnsResourceRecordType.ANY, true, true);
+ if (records.Length == 0)
+ throw new WebServiceException("Zone '" + domain + "' was not found.");
+
+ string authZone = records[0].Name.ToLower();
+
+ if (Zone.DomainEquals(authZone, "resolver-associated-doh.arpa") || Zone.DomainEquals(authZone, "resolver-addresses.arpa"))
+ return;
+
+ using (MemoryStream mS = new MemoryStream())
+ {
+ //serialize zone
+ BinaryWriter bW = new BinaryWriter(mS);
+
+ bW.Write(Encoding.ASCII.GetBytes("DZ")); //format
+ bW.Write((byte)3); //version
+
+ bW.Write(_dnsServer.AuthoritativeZoneRoot.IsZoneDisabled(domain));
+ bW.Write(records.Length);
+
+ foreach (DnsResourceRecord record in records)
+ {
+ record.WriteTo(mS);
+
+ DnsResourceRecordInfo rrInfo = record.Tag as DnsResourceRecordInfo;
+ if (rrInfo == null)
+ rrInfo = new DnsResourceRecordInfo(); //default info
+
+ rrInfo.WriteTo(bW);
+ }
+
+ //write to zone file
+ mS.Position = 0;
+
+ using (FileStream fS = new FileStream(Path.Combine(_configFolder, authZone + ".zone"), FileMode.Create, FileAccess.Write))
+ {
+ mS.CopyTo(fS);
+ }
+ }
+
+ _log.Write("Saved zone file for domain: " + domain);
+ }
+
+ private void DeleteZoneFile(string domain)
+ {
+ domain = domain.ToLower();
+
+ File.Delete(Path.Combine(_configFolder, domain + ".zone"));
+
+ _log.Write("Deleted zone file for domain: " + domain);
+ }
+
+ private void LoadAllowedZoneFile()
+ {
+ string allowedZoneFile = Path.Combine(_configFolder, "allowed.config");
+
+ try
+ {
+ _log.Write("DNS Server is loading allowed zone file: " + allowedZoneFile);
+
+ using (FileStream fS = new FileStream(allowedZoneFile, FileMode.Open, FileAccess.Read))
+ {
+ BinaryReader bR = new BinaryReader(fS);
+
+ if (Encoding.ASCII.GetString(bR.ReadBytes(2)) != "AZ") //format
+ throw new InvalidDataException("DnsServer allowed zone file format is invalid.");
+
+ byte version = bR.ReadByte();
+ switch (version)
+ {
+ case 1:
+ int length = bR.ReadInt32();
+
+ for (int i = 0; i < length; i++)
+ AllowZone(bR.ReadShortString());
+
+ _totalZonesAllowed = length;
+ break;
+
+ default:
+ throw new InvalidDataException("DnsServer allowed zone version not supported.");
+ }
+ }
+
+ _log.Write("DNS Server allowed zone file was loaded: " + allowedZoneFile);
+ }
+ catch (FileNotFoundException)
+ { }
+ catch (Exception ex)
+ {
+ _log.Write("DNS Server encountered an error while loading allowed zone file: " + allowedZoneFile + "\r\n" + ex.ToString());
+ }
+ }
+
+ private void SaveAllowedZoneFile()
+ {
+ ICollection allowedZones = _dnsServer.AllowedZoneRoot.ListAuthoritativeZones();
+
+ _totalZonesAllowed = allowedZones.Count;
+
+ string allowedZoneFile = Path.Combine(_configFolder, "allowed.config");
+
+ using (FileStream fS = new FileStream(allowedZoneFile, FileMode.Create, FileAccess.Write))
+ {
+ BinaryWriter bW = new BinaryWriter(fS);
+
+ bW.Write(Encoding.ASCII.GetBytes("AZ")); //format
+ bW.Write((byte)1); //version
+
+ bW.Write(allowedZones.Count);
+
+ foreach (ZoneInfo zone in allowedZones)
+ bW.WriteShortString(zone.ZoneName);
+ }
+
+ _log.Write("DNS Server allowed zone file was saved: " + allowedZoneFile);
+ }
+
+ private void LoadCustomBlockedZoneFile()
+ {
+ string customBlockedZoneFile = Path.Combine(_configFolder, "custom-blocked.config");
+
+ try
+ {
+ _log.Write("DNS Server is loading custom blocked zone file: " + customBlockedZoneFile);
+
+ using (FileStream fS = new FileStream(customBlockedZoneFile, FileMode.Open, FileAccess.Read))
+ {
+ BinaryReader bR = new BinaryReader(fS);
+
+ if (Encoding.ASCII.GetString(bR.ReadBytes(2)) != "BZ") //format
+ throw new InvalidDataException("DnsServer blocked zone file format is invalid.");
+
+ byte version = bR.ReadByte();
+ switch (version)
+ {
+ case 1:
+ int length = bR.ReadInt32();
+
+ for (int i = 0; i < length; i++)
+ {
+ string zoneName = bR.ReadShortString();
+
+ BlockZone(zoneName, _customBlockedZoneRoot, "custom");
+ BlockZone(zoneName, _dnsServer.BlockedZoneRoot, "custom");
+ }
+
+ _totalZonesBlocked = length;
+ break;
+
+ default:
+ throw new InvalidDataException("DnsServer blocked zone file version not supported.");
+ }
+ }
+
+ _log.Write("DNS Server custom blocked zone file was loaded: " + customBlockedZoneFile);
+ }
+ catch (FileNotFoundException)
+ { }
+ catch (Exception ex)
+ {
+ _log.Write("DNS Server encountered an error while loading custom blocked zone file: " + customBlockedZoneFile + "\r\n" + ex.ToString());
+ }
+ }
+
+ private void SaveCustomBlockedZoneFile()
+ {
+ ICollection customBlockedZones = _customBlockedZoneRoot.ListAuthoritativeZones();
+
+ string customBlockedZoneFile = Path.Combine(_configFolder, "custom-blocked.config");
+
+ using (FileStream fS = new FileStream(customBlockedZoneFile, FileMode.Create, FileAccess.Write))
+ {
+ BinaryWriter bW = new BinaryWriter(fS);
+
+ bW.Write(Encoding.ASCII.GetBytes("BZ")); //format
+ bW.Write((byte)1); //version
+
+ bW.Write(customBlockedZones.Count);
+
+ foreach (ZoneInfo zone in customBlockedZones)
+ bW.WriteShortString(zone.ZoneName);
+ }
+
+ _log.Write("DNS Server custom blocked zone file was saved: " + customBlockedZoneFile);
+ }
+
+ private void LoadBlockLists()
+ {
+ Zone blockedZoneRoot = new Zone(true);
+
+ using (CountdownEvent countdown = new CountdownEvent(_blockListUrls.Count))
+ {
+ foreach (Uri blockListUrl in _blockListUrls)
+ {
+ ThreadPool.QueueUserWorkItem(delegate (object state)
+ {
+ try
+ {
+ LoadBlockListFile(blockedZoneRoot, state as Uri);
+ }
+ catch (Exception ex)
+ {
+ _log.Write(ex);
+ }
+
+ countdown.Signal();
+
+ }, blockListUrl);
+ }
+
+ //load custom blocked zone into new block zone
+ foreach (ZoneInfo zone in _customBlockedZoneRoot.ListAuthoritativeZones())
+ BlockZone(zone.ZoneName, blockedZoneRoot, "custom");
+
+ countdown.Wait();
+ }
+
+ //set new blocked zone
+ _dnsServer.BlockedZoneRoot = blockedZoneRoot;
+ _totalZonesBlocked = blockedZoneRoot.ListAuthoritativeZones().Count;
+
+ _log.Write("DNS Server blocked zone loading finished successfully.");
+ }
+
+ private string GetBlockListFilePath(Uri blockListUrl)
+ {
+ using (HashAlgorithm hash = SHA256.Create())
+ {
+ return Path.Combine(_configFolder, "blocklists", BitConverter.ToString(hash.ComputeHash(Encoding.UTF8.GetBytes(blockListUrl.AbsoluteUri))).Replace("-", "").ToLower());
+ }
+ }
+
+ private void LoadBlockListFile(Zone blockedZoneRoot, Uri blockListUrl)
+ {
+ string blockListAbsoluteUrl = blockListUrl.AbsoluteUri;
+
+ try
+ {
+ string blockListFilePath = GetBlockListFilePath(blockListUrl);
+ int count = 0;
+
+ _log.Write("DNS Server is loading blocked zone from: " + blockListAbsoluteUrl);
+
+ using (FileStream fS = new FileStream(blockListFilePath, FileMode.Open, FileAccess.Read))
+ {
+ //parse hosts file and populate block zone
+ StreamReader sR = new StreamReader(fS, true);
+
+ while (true)
+ {
+ string line = sR.ReadLine();
+ if (line == null)
+ break; //eof
+
+ line = line.TrimStart(' ', '\t');
+
+ if (line == "")
+ continue; //skip empty line
+
+ if (line.StartsWith("#"))
+ continue; //skip comment line
+
+ string firstWord = PopWord(ref line);
+ string secondWord = PopWord(ref line);
+
+ string strIpAddress = null;
+ string hostname;
+
+ if (secondWord == "")
+ {
+ hostname = firstWord;
+ }
+ else
+ {
+ strIpAddress = firstWord;
+ hostname = secondWord;
+ }
+
+ if (!DnsClient.IsDomainNameValid(hostname, false))
+ continue;
+
+ switch (hostname.ToLower())
+ {
+ case "":
+ case "localhost":
+ case "localhost.localdomain":
+ case "local":
+ case "broadcasthost":
+ case "ip6-localhost":
+ case "ip6-loopback":
+ case "ip6-localnet":
+ case "ip6-mcastprefix":
+ case "ip6-allnodes":
+ case "ip6-allrouters":
+ case "ip6-allhosts":
+ continue; //skip these hostnames
+ }
+
+ if (IPAddress.TryParse(hostname, out IPAddress host))
+ continue; //skip line when hostname is IP address
+
+ IPAddress ipAddress;
+
+ if (string.IsNullOrEmpty(strIpAddress) || !IPAddress.TryParse(strIpAddress, out ipAddress))
+ ipAddress = IPAddress.Any;
+
+ if (ipAddress.Equals(IPAddress.Any) || ipAddress.Equals(IPAddress.Loopback) || ipAddress.Equals(IPAddress.IPv6Any) || ipAddress.Equals(IPAddress.IPv6Loopback))
+ {
+ BlockZone(hostname, blockedZoneRoot, blockListAbsoluteUrl);
+ count++;
+ }
+ }
+ }
+
+ _log.Write("DNS Server blocked zone was loaded (" + count + " domains) from: " + blockListAbsoluteUrl);
+ }
+ catch (Exception ex)
+ {
+ _log.Write("DNS Server failed to load block list from: " + blockListAbsoluteUrl + "\r\n" + ex.ToString());
+ }
+ }
+
+ private void UpdateBlockLists()
+ {
+ bool success = false;
+
+ foreach (Uri blockListUrl in _blockListUrls)
+ {
+ string blockListFilePath = GetBlockListFilePath(blockListUrl);
+ string blockListDownloadFilePath = blockListFilePath + ".downloading";
+
+ try
+ {
+ int retries = 1;
+
+ while (true)
+ {
+ if (File.Exists(blockListDownloadFilePath))
+ File.Delete(blockListDownloadFilePath);
+
+ using (WebClientEx wC = new WebClientEx())
+ {
+ wC.Proxy = _dnsServer.Proxy;
+ wC.Timeout = 60000;
+
+ try
+ {
+ wC.DownloadFile(blockListUrl, blockListDownloadFilePath);
+ }
+ catch (WebException)
+ {
+ if (retries < BLOCK_LIST_UPDATE_RETRIES)
+ {
+ retries++;
+ continue;
+ }
+
+ throw;
+ }
+ }
+
+ if (File.Exists(blockListFilePath))
+ File.Delete(blockListFilePath);
+
+ File.Move(blockListDownloadFilePath, blockListFilePath);
+
+ success = true;
+ _log.Write("DNS Server successfully downloaded block list (" + WebUtilities.GetFormattedSize(new FileInfo(blockListFilePath).Length) + "): " + blockListUrl.AbsoluteUri);
+ break;
+ }
+ }
+ catch (Exception ex)
+ {
+ _log.Write("DNS Server failed to download block list and will use previously downloaded file (if available): " + blockListUrl.AbsoluteUri + "\r\n" + ex.ToString());
+ }
+ }
+
+ if (success)
+ {
+ //save last updated on time
+ _blockListLastUpdatedOn = DateTime.UtcNow;
+ SaveConfigFile();
+
+ LoadBlockLists();
+ }
+ }
+
+ private static string PopWord(ref string line)
+ {
+ if (line == "")
+ return line;
+
+ line = line.TrimStart(' ', '\t');
+
+ int i = line.IndexOf(' ');
+
+ if (i < 0)
+ i = line.IndexOf('\t');
+
+ string word;
+
+ if (i < 0)
+ {
+ word = line;
+ line = "";
+ }
+ else
+ {
+ word = line.Substring(0, i);
+ line = line.Substring(i + 1);
+ }
+
+ return word;
+ }
+
+ private void StartBlockListUpdateTimer()
+ {
+ if (_blockListUpdateTimer == null)
+ {
+ _blockListUpdateTimer = new Timer(delegate (object state)
+ {
+ try
+ {
+ if (DateTime.UtcNow > _blockListLastUpdatedOn.AddHours(BLOCK_LIST_UPDATE_AFTER_HOURS))
+ UpdateBlockLists();
+ }
+ catch (Exception ex)
+ {
+ _log.Write("DNS Server encountered an error while updating block list.\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;
+ }
+ }
+
+ private void StartTlsCertificateUpdateTimer()
+ {
+ if (_tlsCertificateUpdateTimer == null)
+ {
+ _tlsCertificateUpdateTimer = new Timer(delegate (object state)
+ {
+ try
+ {
+ FileInfo fileInfo = new FileInfo(_tlsCertificatePath);
+
+ if (fileInfo.Exists && (fileInfo.LastWriteTimeUtc != _tlsCertificateLastModifiedOn))
+ LoadTlsCertificate(_tlsCertificatePath, _tlsCertificatePassword);
+ }
+ catch (Exception ex)
+ {
+ _log.Write("DNS Server encountered an error while updating TLS Certificate: " + _tlsCertificatePath + "\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 LoadTlsCertificate(string tlsCertificatePath, string tlsCertificatePassword)
+ {
+ FileInfo fileInfo = new FileInfo(tlsCertificatePath);
+
+ if (!fileInfo.Exists)
+ throw new ArgumentException("Tls certificate file does not exists: " + tlsCertificatePath);
+
+ if (Path.GetExtension(tlsCertificatePath) != ".pfx")
+ throw new ArgumentException("Tls certificate file must be PKCS #12 formatted with .pfx extension: " + tlsCertificatePath);
+
+ X509Certificate2 certificate = new X509Certificate2(tlsCertificatePath, tlsCertificatePassword);
+
+ if (!certificate.Verify())
+ throw new ArgumentException("Tls certificate is invalid.");
+
+ _dnsServer.Certificate = certificate;
+ _tlsCertificateLastModifiedOn = fileInfo.LastWriteTimeUtc;
+
+ _log.Write("DNS Server TLS certificate was loaded: " + tlsCertificatePath);
+ }
+
+ 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 1:
+ fS.Position = 0;
+ LoadConfigFileV1(fS);
+ break;
+
+ case 2:
+ case 3:
+ case 4:
+ case 5:
+ case 6:
+ case 7:
+ case 8:
+ case 9:
+ _dnsServer.ServerDomain = bR.ReadShortString();
+ _webServicePort = bR.ReadInt32();
+
+ _dnsServer.PreferIPv6 = bR.ReadBoolean();
+
+ if (bR.ReadBoolean()) //logQueries
+ _dnsServer.QueryLogManager = _log;
+
+ _dnsServer.AllowRecursion = bR.ReadBoolean();
+
+ if (version >= 4)
+ _dnsServer.AllowRecursionOnlyForPrivateNetworks = bR.ReadBoolean();
+ else
+ _dnsServer.AllowRecursionOnlyForPrivateNetworks = true; //default true for security reasons
+
+ if (version >= 9)
+ {
+ _dnsServer.CachePrefetchEligibility = bR.ReadInt32();
+ _dnsServer.CachePrefetchTrigger = bR.ReadInt32();
+ _dnsServer.CachePrefetchSampleIntervalInMinutes = bR.ReadInt32();
+ _dnsServer.CachePrefetchSampleEligibilityHitsPerHour = bR.ReadInt32();
+ }
+
+ 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 = new NetProxy(proxyType, address, port, credential);
+ }
+ 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;
+ }
+ }
+
+ _dnsServer.ForwarderProtocol = (DnsTransportProtocol)bR.ReadByte();
+
+ {
+ 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 > 4)
+ {
+ //block list
+ int count = bR.ReadByte();
+
+ for (int i = 0; i < count; i++)
+ _blockListUrls.Add(new Uri(bR.ReadShortString()));
+
+ _blockListLastUpdatedOn = bR.ReadDate();
+
+ if (count > 0)
+ StartBlockListUpdateTimer();
+ }
+
+ if (version >= 6)
+ {
+ int count = bR.ReadByte();
+ _dnsServer.LocalAddresses = new IPAddress[count];
+
+ for (int i = 0; i < count; i++)
+ _dnsServer.LocalAddresses[i] = IPAddressExtension.Parse(bR);
+ }
+ else
+ {
+ _dnsServer.LocalAddresses = new IPAddress[] { IPAddress.Any, IPAddress.IPv6Any };
+ }
+
+ if (version >= 8)
+ {
+ _dnsServer.EnableDnsOverHttp = bR.ReadBoolean();
+ _dnsServer.EnableDnsOverTls = bR.ReadBoolean();
+ _dnsServer.EnableDnsOverHttps = bR.ReadBoolean();
+ _tlsCertificatePath = bR.ReadShortString();
+ _tlsCertificatePassword = bR.ReadShortString();
+
+ if (_tlsCertificatePath == "")
+ _tlsCertificatePath = null;
+
+ if (_tlsCertificatePath != null)
+ {
+ try
+ {
+ LoadTlsCertificate(_tlsCertificatePath, _tlsCertificatePassword);
+ }
+ catch (Exception ex)
+ {
+ _log.Write("DNS Server encountered an error while loading TLS certificate: " + _tlsCertificatePath + "\r\n" + ex.ToString());
+ }
+
+ StartTlsCertificateUpdateTimer();
+ }
+ }
+
+ break;
+
+ default:
+ throw new InvalidDataException("DnsServer 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.");
+
+ _dnsServer.ServerDomain = Environment.MachineName.ToLower();
+ _webServicePort = 5380;
+ _dnsServer.LocalAddresses = new IPAddress[] { IPAddress.Any, IPAddress.IPv6Any };
+
+ SetCredentials("admin", "admin");
+
+ _dnsServer.AllowRecursion = true;
+ _dnsServer.AllowRecursionOnlyForPrivateNetworks = true; //default true for security reasons
+
+ 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.");
+ }
+ }
+
+ private void LoadConfigFileV1(Stream s)
+ {
+ BincodingDecoder decoder = new BincodingDecoder(s, "DS");
+
+ switch (decoder.Version)
+ {
+ case 1:
+ while (true)
+ {
+ Bincoding item = decoder.DecodeNext();
+ if (item.Type == BincodingType.NULL)
+ break;
+
+ if (item.Type == BincodingType.KEY_VALUE_PAIR)
+ {
+ KeyValuePair pair = item.GetKeyValuePair();
+
+ switch (pair.Key)
+ {
+ case "serverDomain":
+ _dnsServer.ServerDomain = pair.Value.GetStringValue();
+ break;
+
+ case "webServicePort":
+ _webServicePort = pair.Value.GetIntegerValue();
+ break;
+
+ case "dnsPreferIPv6":
+ _dnsServer.PreferIPv6 = pair.Value.GetBooleanValue();
+ break;
+
+ case "logQueries":
+ if (pair.Value.GetBooleanValue())
+ _dnsServer.QueryLogManager = _log;
+
+ break;
+
+ case "dnsAllowRecursion":
+ _dnsServer.AllowRecursion = pair.Value.GetBooleanValue();
+ break;
+
+ case "dnsForwarders":
+ ICollection entries = pair.Value.GetList();
+ NameServerAddress[] forwarders = new NameServerAddress[entries.Count];
+
+ int i = 0;
+ foreach (Bincoding entry in entries)
+ forwarders[i++] = new NameServerAddress(IPAddress.Parse(entry.GetStringValue()));
+
+ _dnsServer.Forwarders = forwarders;
+ break;
+
+ case "credentials":
+ foreach (KeyValuePair credential in pair.Value.GetDictionary())
+ SetCredentials(credential.Key, credential.Value.GetStringValue());
+
+ break;
+
+ case "disabledZones":
+ foreach (Bincoding disabledZone in pair.Value.GetList())
+ _dnsServer.AuthoritativeZoneRoot.DisableZone(disabledZone.GetStringValue());
+
+ break;
+ }
+ }
+ }
+ break;
+
+ default:
+ throw new IOException("DNS Config file version not supported: " + decoder.Version);
+ }
+ }
+
+ 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)9); //version
+
+ bW.WriteShortString(_dnsServer.ServerDomain);
+ bW.Write(_webServicePort);
+
+ bW.Write(_dnsServer.PreferIPv6);
+ bW.Write((_dnsServer.QueryLogManager != null)); //logQueries
+ bW.Write(_dnsServer.AllowRecursion);
+ bW.Write(_dnsServer.AllowRecursionOnlyForPrivateNetworks);
+
+ 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);
+ }
+ }
+
+ if (_dnsServer.Forwarders == null)
+ {
+ bW.Write((byte)0);
+ }
+ else
+ {
+ bW.Write(Convert.ToByte(_dnsServer.Forwarders.Length));
+
+ foreach (NameServerAddress forwarder in _dnsServer.Forwarders)
+ forwarder.WriteTo(bW);
+ }
+
+ bW.Write((byte)_dnsServer.ForwarderProtocol);
+
+ {
+ bW.Write(Convert.ToByte(_credentials.Count));
+
+ foreach (KeyValuePair credential in _credentials)
+ {
+ bW.WriteShortString(credential.Key);
+ bW.WriteShortString(credential.Value);
+ }
+ }
+
+ //block list
+ {
+ bW.Write((byte)_blockListUrls.Count);
+
+ foreach (Uri blockListUrl in _blockListUrls)
+ bW.WriteShortString(blockListUrl.AbsoluteUri);
+
+ bW.Write(_blockListLastUpdatedOn);
+ }
+
+ if (_dnsServer.LocalAddresses == null)
+ {
+ bW.Write((byte)0);
+ }
+ else
+ {
+ bW.Write(Convert.ToByte(_dnsServer.LocalAddresses.Length));
+
+ foreach (IPAddress localAddress in _dnsServer.LocalAddresses)
+ localAddress.WriteTo(bW);
+ }
+
+ bW.Write(_dnsServer.EnableDnsOverHttp);
+ bW.Write(_dnsServer.EnableDnsOverTls);
+ bW.Write(_dnsServer.EnableDnsOverHttps);
+
+ if (_tlsCertificatePath == null)
+ bW.WriteShortString(string.Empty);
+ else
+ bW.WriteShortString(_tlsCertificatePath);
+
+ if (_tlsCertificatePassword == null)
+ bW.WriteShortString(string.Empty);
+ else
+ bW.WriteShortString(_tlsCertificatePassword);
+
+ //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 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
+ {
+ //start dns server
+ if (_stats == null)
+ {
+ string statsFolder = Path.Combine(_configFolder, "stats");
+
+ if (!Directory.Exists(statsFolder))
+ Directory.CreateDirectory(statsFolder);
+
+ _stats = new StatsManager(statsFolder, _log);
+ }
+
+ _dnsServer = new DnsServer();
+ _dnsServer.LogManager = _log;
+ _dnsServer.StatsManager = _stats;
+
+ LoadConfigFile();
+ LoadZoneFiles();
+
+ if (_configDisabledZones != null)
+ {
+ foreach (string domain in _configDisabledZones)
+ {
+ _dnsServer.AuthoritativeZoneRoot.DisableZone(domain);
+ SaveZoneFile(domain);
+ }
+ }
+
+ ThreadPool.QueueUserWorkItem(delegate (object state)
+ {
+ try
+ {
+ LoadAllowedZoneFile();
+ LoadCustomBlockedZoneFile();
+ LoadBlockLists();
+ }
+ catch (Exception ex)
+ {
+ _log.Write(ex);
+ }
+ });
+
+ _dnsServer.Start();
+
+ //start dhcp server
+ _dhcpServer = new DhcpServer(Path.Combine(_configFolder, "scopes"));
+ _dhcpServer.AuthoritativeZoneRoot = _dnsServer.AuthoritativeZoneRoot;
+ _dhcpServer.LogManager = _log;
+
+ _dhcpServer.Start();
+
+ //start web service
+ try
+ {
+ _webService = new HttpListener();
+ _webService.Prefixes.Add("http://+:" + _webServicePort + "/");
+ _webService.Start();
+
+ _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());
+
+ _webService = new HttpListener();
+ _webService.Prefixes.Add("http://localhost:" + _webServicePort + "/");
+ _webService.Start();
+
+ _webServiceHostname = "localhost";
+ }
+
+ _webService.IgnoreWriteExceptions = true;
+
+ _webServiceThread = new Thread(AcceptWebRequestAsync);
+ _webServiceThread.IsBackground = true;
+ _webServiceThread.Start();
+
+ _state = ServiceState.Running;
+
+ _log.Write(new IPEndPoint(IPAddress.Any, _webServicePort), "Web Service (v" + _currentVersion + ") was started successfully.");
+ }
+ catch (Exception ex)
+ {
+ _log.Write("Failed to start Web Service (v" + _currentVersion + ")\r\n" + ex.ToString());
+ throw;
+ }
+
+ //Scope scope = new Scope("test", IPAddress.Parse("192.168.120.1"), IPAddress.Parse("192.168.120.100"), IPAddress.Parse("255.255.255.0"), true);
+
+ //scope.RouterAddress = IPAddress.Parse("192.168.120.1");
+ //scope.DnsServers = new IPAddress[] { IPAddress.Parse("192.168.10.4") };
+ //scope.WinsServers = new IPAddress[] { IPAddress.Parse("192.168.10.4") };
+ //scope.NtpServers = new IPAddress[] { IPAddress.Parse("192.168.10.4") };
+ //scope.StaticRoutes = new Dhcp.Options.ClasslessStaticRouteOption.Route[] { new Dhcp.Options.ClasslessStaticRouteOption.Route(IPAddress.Parse("192.168.10.0"), IPAddress.Parse("255.255.255.0"), IPAddress.Parse("192.168.10.4")) };
+ //scope.OfferDelayTime = 2;
+ //scope.DomainName = "local";
+ //scope.Enabled = true;
+ //scope.AddExclusion(IPAddress.Parse("192.168.120.1"), IPAddress.Parse("192.168.120.10"));
+ //scope.AddReservedLease(new byte[] { 0x00, 0x0C, 0x29, 0x36, 0xC9, 0x84 }, IPAddress.Parse("192.168.120.50"));
+
+ //_dhcpServer.AddScope(scope);
+ }
+
+ public void Stop()
+ {
+ if (_state != ServiceState.Running)
+ return;
+
+ _state = ServiceState.Stopping;
+
+ try
+ {
+ _webService.Stop();
+ _dnsServer.Stop();
+
+ StopBlockListUpdateTimer();
+ StopTlsCertificateUpdateTimer();
+
+ _state = ServiceState.Stopped;
+
+ _log.Write(new IPEndPoint(IPAddress.Loopback, _webServicePort), "Web Service (v" + _currentVersion + ") was stopped successfully.");
+ }
+ catch (Exception ex)
+ {
+ _log.Write("Failed to stop Web Service (v" + _currentVersion + ")\r\n" + ex.ToString());
+ throw;
+ }
+ }
+
+ #endregion
+
+ #region properties
+
+ public string ConfigFolder
+ { get { return _configFolder; } }
+
+ public string ServerDomain
+ { get { return _dnsServer.ServerDomain; } }
+
+ public int WebServicePort
+ { get { return _webServicePort; } }
+
+ public string WebServiceHostname
+ { get { return _webServiceHostname; } }
+
+ #endregion
+ }
+}
diff --git a/DnsServerCore/WebServiceException.cs b/DnsServerCore/WebServiceException.cs
new file mode 100644
index 00000000..3a59de54
--- /dev/null
+++ b/DnsServerCore/WebServiceException.cs
@@ -0,0 +1,46 @@
+/*
+Technitium DNS Server
+Copyright (C) 2019 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 System;
+
+namespace DnsServerCore
+{
+ public class WebServiceException : Exception
+ {
+ #region constructors
+
+ public WebServiceException()
+ : base()
+ { }
+
+ public WebServiceException(string message)
+ : base(message)
+ { }
+
+ public WebServiceException(string message, Exception innerException)
+ : base(message, innerException)
+ { }
+
+ protected WebServiceException(System.Runtime.Serialization.SerializationInfo info, System.Runtime.Serialization.StreamingContext context)
+ : base(info, context)
+ { }
+
+ #endregion
+ }
+}