/*
Technitium DNS Server
Copyright (C) 2020 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 DnsServerCore.Dns.ResourceRecords;
using DnsServerCore.Dns.Zones;
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.Net.Http;
using System.Reflection;
using System.Security.Cryptography;
using System.Security.Cryptography.X509Certificates;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using TechnitiumLibrary.IO;
using TechnitiumLibrary.Net;
using TechnitiumLibrary.Net.Dns;
using TechnitiumLibrary.Net.Dns.ResourceRecords;
using TechnitiumLibrary.Net.Proxy;
namespace DnsServerCore
{
public class WebService : IDisposable
{
#region enum
enum ServiceState
{
Stopped = 0,
Starting = 1,
Running = 2,
Stopping = 3
}
#endregion
#region variables
readonly Version _currentVersion;
readonly string _appFolder;
readonly string _configFolder;
readonly Uri _updateCheckUri;
readonly LogManager _log;
DnsServer _dnsServer;
DhcpServer _dhcpServer;
int _webServicePort;
HttpListener _webService;
Thread _webServiceThread;
readonly IndependentTaskScheduler _webServiceTaskScheduler = new IndependentTaskScheduler(ThreadPriority.AboveNormal);
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;
Timer _blockListUpdateTimer;
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;
List _configDisabledZones;
#endregion
#region constructor
public WebService(string configFolder = null, Uri updateCheckUri = null)
{
Assembly assembly = Assembly.GetExecutingAssembly();
AssemblyName assemblyName = assembly.GetName();
_currentVersion = assemblyName.Version;
_appFolder = Path.GetDirectoryName(assembly.Location);
if (configFolder == null)
_configFolder = Path.Combine(_appFolder, "config");
else
_configFolder = configFolder;
if (!Directory.Exists(_configFolder))
Directory.CreateDirectory(_configFolder);
_updateCheckUri = updateCheckUri;
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 (_webService != null)
_webService.Close();
if (_dnsServer != null)
_dnsServer.Dispose();
if (_dhcpServer != null)
_dhcpServer.Dispose();
if (_log != null)
_log.Dispose();
}
_disposed = true;
}
public void Dispose()
{
Dispose(true);
}
#endregion
#region private
private void AcceptWebRequestAsync(object state)
{
try
{
while (true)
{
HttpListenerContext context = _webService.GetContext();
_ = Task.Factory.StartNew(delegate ()
{
return ProcessRequestAsync(context.Request, context.Response);
}, CancellationToken.None, TaskCreationOptions.DenyChildAttach, _webServiceTaskScheduler);
}
}
catch (Exception ex)
{
if ((_state == ServiceState.Stopping) || (_state == ServiceState.Stopped))
return; //web service stopping
_log.Write(ex);
throw;
}
}
private async Task ProcessRequestAsync(HttpListenerRequest request, HttpListenerResponse response)
{
response.AddHeader("Server", "");
response.AddHeader("X-Robots-Tag", "noindex, nofollow");
try
{
Uri url = request.Url;
string path = url.AbsolutePath;
if (!path.StartsWith("/") || path.Contains("/../") || path.Contains("/.../"))
{
await SendErrorAsync(response, 404);
return;
}
if (path.StartsWith("/api/"))
{
using (MemoryStream mS = new MemoryStream())
{
try
{
JsonTextWriter jsonWriter = new JsonTextWriter(new StreamWriter(mS));
jsonWriter.WriteStartObject();
switch (path)
{
case "/api/login":
await LoginAsync(request, jsonWriter);
break;
case "/api/logout":
Logout(request);
break;
default:
if (!IsSessionValid(request))
throw new InvalidTokenWebServiceException("Invalid token or session expired.");
jsonWriter.WritePropertyName("response");
jsonWriter.WriteStartObject();
try
{
switch (path)
{
case "/api/changePassword":
ChangePassword(request);
break;
case "/api/checkForUpdate":
await CheckForUpdateAsync(request, jsonWriter);
break;
case "/api/getDnsSettings":
GetDnsSettings(jsonWriter);
break;
case "/api/setDnsSettings":
SetDnsSettings(request, jsonWriter);
break;
case "/api/getStats":
await 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/deleteAllowedZone":
DeleteAllowedZone(request);
break;
case "/api/allowZone":
AllowZone(request);
break;
case "/api/listBlockedZones":
ListBlockedZones(request, jsonWriter);
break;
case "/api/importBlockedZones":
ImportBlockedZones(request);
break;
case "/api/exportBlockedZones":
ExportBlockedZones(response);
return;
case "/api/deleteBlockedZone":
DeleteBlockedZone(request);
break;
case "/api/blockZone":
BlockZone(request);
break;
case "/api/listZones":
ListZones(jsonWriter);
break;
case "/api/createZone":
await CreateZoneAsync(request, jsonWriter);
break;
case "/api/deleteZone":
DeleteZone(request);
break;
case "/api/enableZone":
EnableZone(request);
break;
case "/api/disableZone":
DisableZone(request);
break;
case "/api/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":
await 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":
await SetDhcpScopeAsync(request);
break;
case "/api/enableDhcpScope":
await EnableDhcpScopeAsync(request);
break;
case "/api/disableDhcpScope":
DisableDhcpScope(request);
break;
case "/api/deleteDhcpScope":
DeleteDhcpScope(request);
break;
default:
await SendErrorAsync(response, 404);
return;
}
}
finally
{
jsonWriter.WriteEndObject();
}
break;
}
jsonWriter.WritePropertyName("status");
jsonWriter.WriteValue("ok");
jsonWriter.WriteEndObject();
jsonWriter.Flush();
}
catch (InvalidTokenWebServiceException ex)
{
mS.SetLength(0);
JsonTextWriter jsonWriter = new JsonTextWriter(new StreamWriter(mS));
jsonWriter.WriteStartObject();
jsonWriter.WritePropertyName("status");
jsonWriter.WriteValue("invalid-token");
jsonWriter.WritePropertyName("errorMessage");
jsonWriter.WriteValue(ex.Message);
jsonWriter.WriteEndObject();
jsonWriter.Flush();
}
catch (Exception ex)
{
_log.Write(GetRequestRemoteEndPoint(request), ex);
mS.SetLength(0);
JsonTextWriter jsonWriter = new JsonTextWriter(new StreamWriter(mS));
jsonWriter.WriteStartObject();
jsonWriter.WritePropertyName("status");
jsonWriter.WriteValue("error");
jsonWriter.WritePropertyName("errorMessage");
jsonWriter.WriteValue(ex.Message);
jsonWriter.WritePropertyName("stackTrace");
jsonWriter.WriteValue(ex.StackTrace);
if (ex.InnerException != null)
{
jsonWriter.WritePropertyName("innerErrorMessage");
jsonWriter.WriteValue(ex.InnerException.Message);
}
jsonWriter.WriteEndObject();
jsonWriter.Flush();
}
response.ContentType = "application/json; charset=utf-8";
response.ContentEncoding = Encoding.UTF8;
response.ContentLength64 = mS.Length;
using (Stream stream = response.OutputStream)
{
mS.WriteTo(response.OutputStream);
}
}
}
else if (path.StartsWith("/log/"))
{
if (!IsSessionValid(request))
{
await SendErrorAsync(response, 403, "Invalid token or session expired.");
return;
}
string[] pathParts = path.Split('/');
string logFileName = pathParts[2];
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 == "/")
{
path = "/index.html";
}
else if ((path == "/blocklist.txt") && !IPAddress.IsLoopback(GetRequestRemoteEndPoint(request).Address))
{
await SendErrorAsync(response, 403);
return;
}
string wwwroot = Path.Combine(_appFolder, "www");
path = Path.GetFullPath(wwwroot + path.Replace('/', Path.DirectorySeparatorChar));
if (!path.StartsWith(wwwroot) || !File.Exists(path))
{
await SendErrorAsync(response, 404);
return;
}
await SendFileAsync(response, path);
}
}
catch (Exception ex)
{
if ((_state == ServiceState.Stopping) || (_state == ServiceState.Stopped))
return; //web service stopping
_log.Write(GetRequestRemoteEndPoint(request), ex);
await SendError(response, ex);
}
}
private static IPEndPoint GetRequestRemoteEndPoint(HttpListenerRequest request)
{
try
{
if (request.RemoteEndPoint == null)
return new IPEndPoint(IPAddress.Any, 0);
if (NetUtilities.IsPrivateIP(request.RemoteEndPoint.Address))
{
string xRealIp = request.Headers["X-Real-IP"];
if (IPAddress.TryParse(xRealIp, out IPAddress address))
{
//get the real IP address of the requesting client from X-Real-IP header set in nginx proxy_pass block
return new IPEndPoint(address, 0);
}
}
return request.RemoteEndPoint;
}
catch
{
return new IPEndPoint(IPAddress.Any, 0);
}
}
private static Task SendError(HttpListenerResponse response, Exception ex)
{
return SendErrorAsync(response, 500, ex.ToString());
}
private static async Task SendErrorAsync(HttpListenerResponse response, int statusCode, string message = null)
{
try
{
string statusString = statusCode + " " + DnsServer.GetHttpStatusString((HttpStatusCode)statusCode);
byte[] buffer = Encoding.UTF8.GetBytes("" + statusString + "" + statusString + "
" + (message == null ? "" : "" + message + "
") + "");
response.StatusCode = statusCode;
response.ContentType = "text/html";
response.ContentLength64 = buffer.Length;
using (Stream stream = response.OutputStream)
{
await stream.WriteAsync(buffer, 0, buffer.Length);
}
}
catch
{ }
}
private static async Task SendFileAsync(HttpListenerResponse response, string filePath)
{
using (FileStream fS = new FileStream(filePath, FileMode.Open, FileAccess.Read, FileShare.ReadWrite))
{
response.ContentType = WebUtilities.GetContentType(filePath).MediaType;
response.ContentLength64 = fS.Length;
response.AddHeader("Cache-Control", "private, max-age=300");
using (Stream stream = response.OutputStream)
{
try
{
await fS.CopyToAsync(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 async Task LoginAsync(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);
await Task.Delay(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);
}
private async Task CheckForUpdateAsync(HttpListenerRequest request, JsonTextWriter jsonWriter)
{
Version updateVersion = null;
string displayText = null;
string downloadLink = null;
bool updateAvailable = false;
if (_updateCheckUri != null)
{
try
{
HttpClientHandler handler = new HttpClientHandler();
handler.Proxy = _dnsServer.Proxy;
using (HttpClient http = new HttpClient(handler))
{
byte[] response = await http.GetByteArrayAsync(_updateCheckUri);
using (MemoryStream mS = new MemoryStream(response, false))
{
BinaryReader bR = new BinaryReader(mS);
if (Encoding.ASCII.GetString(bR.ReadBytes(2)) != "DU") //format
throw new InvalidDataException("DNS Server update info format is invalid.");
switch (bR.ReadByte()) //version
{
case 2:
updateVersion = new Version(bR.ReadShortString());
displayText = bR.ReadShortString();
downloadLink = bR.ReadShortString();
break;
default:
throw new InvalidDataException("DNS Server update info version not supported.");
}
updateAvailable = updateVersion > _currentVersion;
}
}
_log.Write(GetRequestRemoteEndPoint(request), "Check for update was done {updateAvailable: " + updateAvailable + "; updateVersion: " + updateVersion + "; displayText: " + displayText + "; downloadLink: " + downloadLink + ";}");
}
catch (Exception ex)
{
_log.Write(GetRequestRemoteEndPoint(request), "Check for update was done {updateAvailable: False;}\r\n" + ex.ToString());
}
}
jsonWriter.WritePropertyName("updateAvailable");
jsonWriter.WriteValue(updateAvailable);
if (updateAvailable)
{
if (!string.IsNullOrEmpty(displayText))
{
jsonWriter.WritePropertyName("displayText");
jsonWriter.WriteValue(displayText);
}
jsonWriter.WritePropertyName("downloadLink");
jsonWriter.WriteValue(downloadLink);
}
}
private static string GetCleanVersion(Version version)
{
string strVersion = version.Major + "." + version.Minor;
if (version.Build > 0)
strVersion += "." + version.Build;
if (version.Revision > 0)
strVersion += "." + version.Revision;
return strVersion;
}
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("dnsServerLocalEndPoints");
jsonWriter.WriteStartArray();
foreach (IPEndPoint localEP in _dnsServer.LocalEndPoints)
jsonWriter.WriteValue(localEP.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.WritePropertyName("bypass");
jsonWriter.WriteStartArray();
foreach (NetProxyBypassItem item in proxy.BypassList)
jsonWriter.WriteValue(item.Value);
jsonWriter.WriteEndArray();
jsonWriter.WriteEndObject();
}
jsonWriter.WritePropertyName("forwarders");
DnsTransportProtocol forwarderProtocol = DnsTransportProtocol.Udp;
if (_dnsServer.Forwarders == null)
{
jsonWriter.WriteNull();
}
else
{
forwarderProtocol = _dnsServer.Forwarders[0].Protocol;
jsonWriter.WriteStartArray();
foreach (NameServerAddress forwarder in _dnsServer.Forwarders)
jsonWriter.WriteValue(forwarder.OriginalAddress);
jsonWriter.WriteEndArray();
}
jsonWriter.WritePropertyName("forwarderProtocol");
jsonWriter.WriteValue(forwarderProtocol.ToString());
jsonWriter.WritePropertyName("blockListUrls");
if (_dnsServer.BlockListZoneManager.BlockListUrls.Count == 0)
{
jsonWriter.WriteNull();
}
else
{
jsonWriter.WriteStartArray();
foreach (Uri blockListUrl in _dnsServer.BlockListZoneManager.BlockListUrls)
jsonWriter.WriteValue(blockListUrl.AbsoluteUri);
jsonWriter.WriteEndArray();
}
}
private void SetDnsSettings(HttpListenerRequest request, JsonTextWriter jsonWriter)
{
string strServerDomain = request.QueryString["serverDomain"];
if (!string.IsNullOrEmpty(strServerDomain))
_dnsServer.ServerDomain = strServerDomain;
string strDnsServerLocalEndPoints = request.QueryString["dnsServerLocalEndPoints"];
if (strDnsServerLocalEndPoints != null)
{
if (string.IsNullOrEmpty(strDnsServerLocalEndPoints))
strDnsServerLocalEndPoints = "0.0.0.0:53,[::]:53";
string[] strLocalEndPoints = strDnsServerLocalEndPoints.Split(new char[] { ',' }, StringSplitOptions.RemoveEmptyEntries);
List localEndPoints = new List(strLocalEndPoints.Length);
for (int i = 0; i < strLocalEndPoints.Length; i++)
{
NameServerAddress nameServer = new NameServerAddress(strLocalEndPoints[i]);
if (nameServer.IPEndPoint != null)
localEndPoints.Add(nameServer.IPEndPoint);
}
_dnsServer.LocalEndPoints = localEndPoints;
}
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 = NetProxy.CreateProxy(proxyType, request.QueryString["proxyAddress"], int.Parse(request.QueryString["proxyPort"]), credential);
string strProxyBypass = request.QueryString["proxyBypass"];
if (!string.IsNullOrEmpty(strProxyBypass))
{
string[] strBypassList = strProxyBypass.Split(new char[] { ',' }, StringSplitOptions.RemoveEmptyEntries);
_dnsServer.Proxy.BypassList.Clear();
for (int i = 0; i < strBypassList.Length; i++)
_dnsServer.Proxy.BypassList.Add(new NetProxyBypassItem(strBypassList[i]));
}
}
}
DnsTransportProtocol forwarderProtocol = DnsTransportProtocol.Udp;
string strForwarderProtocol = request.QueryString["forwarderProtocol"];
if (!string.IsNullOrEmpty(strForwarderProtocol))
forwarderProtocol = (DnsTransportProtocol)Enum.Parse(typeof(DnsTransportProtocol), strForwarderProtocol, true);
string strForwarders = request.QueryString["forwarders"];
if (!string.IsNullOrEmpty(strForwarders))
{
if (strForwarders == "false")
{
_dnsServer.Forwarders = null;
}
else
{
string[] strForwardersList = strForwarders.Split(new char[] { ',' }, StringSplitOptions.RemoveEmptyEntries);
NameServerAddress[] forwarders = new NameServerAddress[strForwardersList.Length];
for (int i = 0; i < strForwardersList.Length; i++)
{
if ((forwarderProtocol == DnsTransportProtocol.Tls) && IPAddress.TryParse(strForwardersList[i], out _))
strForwardersList[i] += ":853";
forwarders[i] = new NameServerAddress(strForwardersList[i], forwarderProtocol);
}
_dnsServer.Forwarders = forwarders;
}
}
string strBlockListUrls = request.QueryString["blockListUrls"];
if (!string.IsNullOrEmpty(strBlockListUrls))
{
if (strBlockListUrls == "false")
{
StopBlockListUpdateTimer();
_dnsServer.BlockListZoneManager.BlockListUrls.Clear();
_dnsServer.BlockListZoneManager.Flush();
}
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 != _dnsServer.BlockListZoneManager.BlockListUrls.Count)
{
updated = true;
}
else
{
foreach (string strBlockListUrl in strBlockListUrlList)
{
if (!_dnsServer.BlockListZoneManager.BlockListUrls.Contains(new Uri(strBlockListUrl)))
{
updated = true;
break;
}
}
}
}
if (updated)
{
_dnsServer.BlockListZoneManager.BlockListUrls.Clear();
foreach (string strBlockListUrl in strBlockListUrlList)
{
Uri blockListUrl = new Uri(strBlockListUrl);
if (!_dnsServer.BlockListZoneManager.BlockListUrls.Contains(blockListUrl))
_dnsServer.BlockListZoneManager.BlockListUrls.Add(blockListUrl);
}
_blockListLastUpdatedOn = new DateTime();
StopBlockListUpdateTimer();
StartBlockListUpdateTimer();
}
}
}
_log.Write(GetRequestRemoteEndPoint(request), "[" + GetSession(request).Username + "] DNS Settings were updated {serverDomain: " + _dnsServer.ServerDomain + "; dnsServerLocalEndPoints: " + strDnsServerLocalEndPoints + "; 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 async Task GetStats(HttpListenerRequest request, JsonTextWriter jsonWriter)
{
string strType = request.QueryString["type"];
if (string.IsNullOrEmpty(strType))
strType = "lastHour";
Dictionary>> data;
switch (strType)
{
case "lastHour":
data = _dnsServer.StatsManager.GetLastHourStats();
break;
case "lastDay":
data = _dnsServer.StatsManager.GetLastDayStats();
break;
case "lastWeek":
data = _dnsServer.StatsManager.GetLastWeekStats();
break;
case "lastMonth":
data = _dnsServer.StatsManager.GetLastMonthStats();
break;
case "lastYear":
data = _dnsServer.StatsManager.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(_dnsServer.AllowedZoneManager.TotalZonesAllowed);
jsonWriter.WritePropertyName("blockedZones");
jsonWriter.WriteValue(_dnsServer.BlockedZoneManager.TotalZonesBlocked + _dnsServer.BlockListZoneManager.TotalZonesBlocked);
jsonWriter.WriteEndObject();
}
//main chart
{
jsonWriter.WritePropertyName("mainChartData");
jsonWriter.WriteStartObject();
//label
{
List> statsPerInterval = data["totalQueriesPerInterval"];
jsonWriter.WritePropertyName("labels");
jsonWriter.WriteStartArray();
foreach (KeyValuePair item in statsPerInterval)
jsonWriter.WriteValue(item.Key);
jsonWriter.WriteEndArray();
}
//datasets
{
jsonWriter.WritePropertyName("datasets");
jsonWriter.WriteStartArray();
WriteChartDataSet(jsonWriter, "Total", "rgba(102, 153, 255, 0.1)", "rgb(102, 153, 255)", data["totalQueriesPerInterval"]);
WriteChartDataSet(jsonWriter, "No Error", "rgba(92, 184, 92, 0.1)", "rgb(92, 184, 92)", data["totalNoErrorPerInterval"]);
WriteChartDataSet(jsonWriter, "Server Failure", "rgba(217, 83, 79, 0.1)", "rgb(217, 83, 79)", data["totalServerFailurePerInterval"]);
WriteChartDataSet(jsonWriter, "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, "Authoritative", "rgba(150, 150, 0, 0.1)", "rgb(150, 150, 0)", data["totalAuthHitPerInterval"]);
WriteChartDataSet(jsonWriter, "Recursive", "rgba(23, 162, 184, 0.1)", "rgb(23, 162, 184)", data["totalRecursionsPerInterval"]);
WriteChartDataSet(jsonWriter, "Cached", "rgba(111, 84, 153, 0.1)", "rgb(111, 84, 153)", data["totalCacheHitPerInterval"]);
WriteChartDataSet(jsonWriter, "Blocked", "rgba(255, 165, 0, 0.1)", "rgb(255, 165, 0)", data["totalBlockedPerInterval"]);
WriteChartDataSet(jsonWriter, "Clients", "rgba(51, 122, 183, 0.1)", "rgb(51, 122, 183)", data["totalClientsPerInterval"]);
jsonWriter.WriteEndArray();
}
jsonWriter.WriteEndObject();
}
//query response chart
{
jsonWriter.WritePropertyName("queryResponseChartData");
jsonWriter.WriteStartObject();
List> stats = data["stats"];
//labels
{
jsonWriter.WritePropertyName("labels");
jsonWriter.WriteStartArray();
foreach (KeyValuePair item in stats)
{
switch (item.Key)
{
case "totalAuthoritative":
jsonWriter.WriteValue("Authoritative");
break;
case "totalRecursive":
jsonWriter.WriteValue("Recursive");
break;
case "totalCached":
jsonWriter.WriteValue("Cached");
break;
case "totalBlocked":
jsonWriter.WriteValue("Blocked");
break;
}
}
jsonWriter.WriteEndArray();
}
//datasets
{
jsonWriter.WritePropertyName("datasets");
jsonWriter.WriteStartArray();
jsonWriter.WriteStartObject();
jsonWriter.WritePropertyName("data");
jsonWriter.WriteStartArray();
foreach (KeyValuePair item in stats)
{
switch (item.Key)
{
case "totalAuthoritative":
case "totalRecursive":
case "totalCached":
case "totalBlocked":
jsonWriter.WriteValue(item.Value);
break;
}
}
jsonWriter.WriteEndArray();
jsonWriter.WritePropertyName("backgroundColor");
jsonWriter.WriteStartArray();
jsonWriter.WriteValue("rgba(150, 150, 0, 0.5)");
jsonWriter.WriteValue("rgba(23, 162, 184, 0.5)");
jsonWriter.WriteValue("rgba(111, 84, 153, 0.5)");
jsonWriter.WriteValue("rgba(255, 165, 0, 0.5)");
jsonWriter.WriteEndArray();
jsonWriter.WriteEndObject();
jsonWriter.WriteEndArray();
}
jsonWriter.WriteEndObject();
}
//query type chart
{
jsonWriter.WritePropertyName("queryTypeChartData");
jsonWriter.WriteStartObject();
List> queryTypes = data["queryTypes"];
//labels
{
jsonWriter.WritePropertyName("labels");
jsonWriter.WriteStartArray();
foreach (KeyValuePair item in queryTypes)
jsonWriter.WriteValue(item.Key);
jsonWriter.WriteEndArray();
}
//datasets
{
jsonWriter.WritePropertyName("datasets");
jsonWriter.WriteStartArray();
jsonWriter.WriteStartObject();
jsonWriter.WritePropertyName("data");
jsonWriter.WriteStartArray();
foreach (KeyValuePair item in queryTypes)
jsonWriter.WriteValue(item.Value);
jsonWriter.WriteEndArray();
jsonWriter.WritePropertyName("backgroundColor");
jsonWriter.WriteStartArray();
jsonWriter.WriteValue("rgba(102, 153, 255, 0.5)");
jsonWriter.WriteValue("rgba(92, 184, 92, 0.5)");
jsonWriter.WriteValue("rgba(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"];
IDictionary clientIpMap = _dhcpServer.GetAddressClientMap();
jsonWriter.WritePropertyName("topClients");
jsonWriter.WriteStartArray();
foreach (KeyValuePair item in topClients)
{
jsonWriter.WriteStartObject();
jsonWriter.WritePropertyName("name");
jsonWriter.WriteValue(item.Key);
if (clientIpMap.TryGetValue(item.Key, out string clientDomain))
{
jsonWriter.WritePropertyName("domain");
jsonWriter.WriteValue(clientDomain);
}
else
{
IPAddress address = IPAddress.Parse(item.Key);
if (IPAddress.IsLoopback(address))
{
jsonWriter.WritePropertyName("domain");
jsonWriter.WriteValue("localhost");
}
else
{
try
{
DnsDatagram ptrResponse = await _dnsServer.DirectQueryAsync(new DnsQuestionRecord(address, DnsClass.IN), 200);
if ((ptrResponse != null) && (ptrResponse.Answer.Count > 0))
{
IReadOnlyList ptrDomains = DnsClient.ParseResponsePTR(ptrResponse);
if (ptrDomains != null)
{
jsonWriter.WritePropertyName("domain");
jsonWriter.WriteValue(ptrDomains[0]);
}
}
}
catch
{ }
}
}
jsonWriter.WritePropertyName("hits");
jsonWriter.WriteValue(item.Value);
jsonWriter.WriteEndObject();
}
jsonWriter.WriteEndArray();
}
//top domains
{
List> topDomains = data["topDomains"];
jsonWriter.WritePropertyName("topDomains");
jsonWriter.WriteStartArray();
foreach (KeyValuePair item in topDomains)
{
jsonWriter.WriteStartObject();
jsonWriter.WritePropertyName("name");
jsonWriter.WriteValue(item.Key);
jsonWriter.WritePropertyName("hits");
jsonWriter.WriteValue(item.Value);
jsonWriter.WriteEndObject();
}
jsonWriter.WriteEndArray();
}
//top blocked domains
{
List> topBlockedDomains = data["topBlockedDomains"];
jsonWriter.WritePropertyName("topBlockedDomains");
jsonWriter.WriteStartArray();
foreach (KeyValuePair item in topBlockedDomains)
{
jsonWriter.WriteStartObject();
jsonWriter.WritePropertyName("name");
jsonWriter.WriteValue(item.Key);
jsonWriter.WritePropertyName("hits");
jsonWriter.WriteValue(item.Value);
jsonWriter.WriteEndObject();
}
jsonWriter.WriteEndArray();
}
}
private static void WriteChartDataSet(JsonTextWriter jsonWriter, string label, string backgroundColor, string borderColor, List> statsPerInterval)
{
jsonWriter.WriteStartObject();
jsonWriter.WritePropertyName("label");
jsonWriter.WriteValue(label);
jsonWriter.WritePropertyName("backgroundColor");
jsonWriter.WriteValue(backgroundColor);
jsonWriter.WritePropertyName("borderColor");
jsonWriter.WriteValue(borderColor);
jsonWriter.WritePropertyName("borderWidth");
jsonWriter.WriteValue(2);
jsonWriter.WritePropertyName("fill");
jsonWriter.WriteValue(true);
jsonWriter.WritePropertyName("data");
jsonWriter.WriteStartArray();
foreach (KeyValuePair item in statsPerInterval)
jsonWriter.WriteValue(item.Value);
jsonWriter.WriteEndArray();
jsonWriter.WriteEndObject();
}
private void FlushCache(HttpListenerRequest request)
{
_dnsServer.CacheZoneManager.Flush();
_log.Write(GetRequestRemoteEndPoint(request), "[" + GetSession(request).Username + "] Cache was flushed.");
}
private void ListCachedZones(HttpListenerRequest request, JsonTextWriter jsonWriter)
{
string domain = request.QueryString["domain"];
if (domain == null)
domain = "";
string direction = request.QueryString["direction"];
List subZones;
List records;
while (true)
{
subZones = _dnsServer.CacheZoneManager.ListSubDomains(domain);
records = _dnsServer.CacheZoneManager.ListAllRecords(domain);
if (records.Count > 0)
break;
if (subZones.Count != 1)
break;
if (direction == "up")
{
if (domain.Length == 0)
break;
int i = domain.IndexOf('.');
if (i < 0)
domain = "";
else
domain = domain.Substring(i + 1);
}
else if (domain.Length == 0)
{
domain = subZones[0];
}
else
{
domain = subZones[0] + "." + domain;
}
}
subZones.Sort();
jsonWriter.WritePropertyName("domain");
jsonWriter.WriteValue(domain);
jsonWriter.WritePropertyName("zones");
jsonWriter.WriteStartArray();
if (domain.Length != 0)
domain = "." + domain;
foreach (string subZone in subZones)
jsonWriter.WriteValue(subZone + domain);
jsonWriter.WriteEndArray();
WriteRecordsAsJson(records, jsonWriter, false);
}
private void DeleteCachedZone(HttpListenerRequest request)
{
string domain = request.QueryString["domain"];
if (string.IsNullOrEmpty(domain))
throw new WebServiceException("Parameter 'domain' missing.");
if (_dnsServer.CacheZoneManager.DeleteZone(domain))
_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"];
List subZones;
IReadOnlyList records;
while (true)
{
subZones = _dnsServer.AllowedZoneManager.ListSubDomains(domain);
records = _dnsServer.AllowedZoneManager.QueryRecords(domain, DnsResourceRecordType.ANY);
if (records.Count > 0)
break;
if (subZones.Count != 1)
break;
if (direction == "up")
{
if (domain.Length == 0)
break;
int i = domain.IndexOf('.');
if (i < 0)
domain = "";
else
domain = domain.Substring(i + 1);
}
else if (domain.Length == 0)
{
domain = subZones[0];
}
else
{
domain = subZones[0] + "." + domain;
}
}
subZones.Sort();
jsonWriter.WritePropertyName("domain");
jsonWriter.WriteValue(domain);
jsonWriter.WritePropertyName("zones");
jsonWriter.WriteStartArray();
if (domain.Length != 0)
domain = "." + domain;
foreach (string subZone in subZones)
jsonWriter.WriteValue(subZone + domain);
jsonWriter.WriteEndArray();
WriteRecordsAsJson(new List(records), jsonWriter, false);
}
private void ImportAllowedZones(HttpListenerRequest request)
{
if (!request.ContentType.StartsWith("application/x-www-form-urlencoded"))
throw new 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(',');
bool added = false;
foreach (string allowedZone in allowedZones)
{
if (_dnsServer.AllowedZoneManager.AllowZone(allowedZone))
added = true;
}
if (added)
{
_log.Write(GetRequestRemoteEndPoint(request), "[" + GetSession(request).Username + "] Total " + allowedZones.Length + " zones were imported into allowed zone successfully.");
_dnsServer.AllowedZoneManager.SaveZoneFile();
}
return;
}
}
throw new WebServiceException("Parameter 'allowedZones' missing.");
}
private void ExportAllowedZones(HttpListenerResponse response)
{
IReadOnlyList zoneInfoList = _dnsServer.AllowedZoneManager.ListZones();
response.ContentType = "text/plain";
response.AddHeader("Content-Disposition", "attachment;filename=AllowedZones.txt");
using (StreamWriter sW = new StreamWriter(new BufferedStream(response.OutputStream)))
{
foreach (AuthZoneInfo zoneInfo in zoneInfoList)
sW.WriteLine(zoneInfo.Name);
}
}
private void DeleteAllowedZone(HttpListenerRequest request)
{
string domain = request.QueryString["domain"];
if (string.IsNullOrEmpty(domain))
throw new WebServiceException("Parameter 'domain' missing.");
if (_dnsServer.AllowedZoneManager.DeleteZone(domain))
{
_log.Write(GetRequestRemoteEndPoint(request), "[" + GetSession(request).Username + "] Allowed zone was deleted: " + domain);
_dnsServer.AllowedZoneManager.SaveZoneFile();
}
}
private void AllowZone(HttpListenerRequest request)
{
string domain = request.QueryString["domain"];
if (string.IsNullOrEmpty(domain))
throw new WebServiceException("Parameter 'domain' missing.");
if (IPAddress.TryParse(domain, out IPAddress ipAddress))
domain = (new DnsQuestionRecord(ipAddress, DnsClass.IN)).Name;
if (_dnsServer.AllowedZoneManager.AllowZone(domain))
{
_log.Write(GetRequestRemoteEndPoint(request), "[" + GetSession(request).Username + "] Zone was allowed: " + domain);
_dnsServer.AllowedZoneManager.SaveZoneFile();
}
}
private void ListBlockedZones(HttpListenerRequest request, JsonTextWriter jsonWriter)
{
string domain = request.QueryString["domain"];
if (domain == null)
domain = "";
string direction = request.QueryString["direction"];
List subZones;
IReadOnlyList records;
while (true)
{
subZones = _dnsServer.BlockedZoneManager.ListSubDomains(domain);
records = _dnsServer.BlockedZoneManager.QueryRecords(domain, DnsResourceRecordType.ANY);
if (records.Count > 0)
break;
if (subZones.Count != 1)
break;
if (direction == "up")
{
if (domain.Length == 0)
break;
int i = domain.IndexOf('.');
if (i < 0)
domain = "";
else
domain = domain.Substring(i + 1);
}
else if (domain.Length == 0)
{
domain = subZones[0];
}
else
{
domain = subZones[0] + "." + domain;
}
}
subZones.Sort();
jsonWriter.WritePropertyName("domain");
jsonWriter.WriteValue(domain);
jsonWriter.WritePropertyName("zones");
jsonWriter.WriteStartArray();
if (domain.Length != 0)
domain = "." + domain;
foreach (string subZone in subZones)
jsonWriter.WriteValue(subZone + domain);
jsonWriter.WriteEndArray();
WriteRecordsAsJson(new List(records), jsonWriter, false);
}
private void ImportBlockedZones(HttpListenerRequest request)
{
if (!request.ContentType.StartsWith("application/x-www-form-urlencoded"))
throw new 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(',');
bool added = false;
foreach (string blockedZone in blockedZones)
{
if (_dnsServer.BlockedZoneManager.BlockZone(blockedZone))
added = true;
}
if (added)
{
_log.Write(GetRequestRemoteEndPoint(request), "[" + GetSession(request).Username + "] Total " + blockedZones.Length + " zones were imported into blocked zone successfully.");
_dnsServer.BlockedZoneManager.SaveZoneFile();
}
return;
}
}
throw new WebServiceException("Parameter 'blockedZones' missing.");
}
private void ExportBlockedZones(HttpListenerResponse response)
{
IReadOnlyList zoneInfoList = _dnsServer.BlockedZoneManager.ListZones();
response.ContentType = "text/plain";
response.AddHeader("Content-Disposition", "attachment;filename=BlockedZones.txt");
using (StreamWriter sW = new StreamWriter(new BufferedStream(response.OutputStream)))
{
foreach (AuthZoneInfo zoneInfo in zoneInfoList)
sW.WriteLine(zoneInfo.Name);
}
}
private void DeleteBlockedZone(HttpListenerRequest request)
{
string domain = request.QueryString["domain"];
if (string.IsNullOrEmpty(domain))
throw new WebServiceException("Parameter 'domain' missing.");
if (_dnsServer.BlockedZoneManager.DeleteZone(domain))
{
_log.Write(GetRequestRemoteEndPoint(request), "[" + GetSession(request).Username + "] Blocked zone was deleted: " + domain);
_dnsServer.BlockedZoneManager.SaveZoneFile();
}
}
private void BlockZone(HttpListenerRequest request)
{
string domain = request.QueryString["domain"];
if (string.IsNullOrEmpty(domain))
throw new WebServiceException("Parameter 'domain' missing.");
if (IPAddress.TryParse(domain, out IPAddress ipAddress))
domain = (new DnsQuestionRecord(ipAddress, DnsClass.IN)).Name;
if (_dnsServer.BlockedZoneManager.BlockZone(domain))
{
_log.Write(GetRequestRemoteEndPoint(request), "[" + GetSession(request).Username + "] Domain was added to blocked zone: " + domain);
_dnsServer.BlockedZoneManager.SaveZoneFile();
}
}
private void ListZones(JsonTextWriter jsonWriter)
{
List zones = _dnsServer.AuthZoneManager.ListZones();
zones.Sort();
jsonWriter.WritePropertyName("zones");
jsonWriter.WriteStartArray();
foreach (AuthZoneInfo zone in zones)
{
jsonWriter.WriteStartObject();
jsonWriter.WritePropertyName("name");
jsonWriter.WriteValue(zone.Name);
jsonWriter.WritePropertyName("type");
jsonWriter.WriteValue(zone.Type.ToString());
switch (zone.Type)
{
case AuthZoneType.Primary:
jsonWriter.WritePropertyName("internal");
jsonWriter.WriteValue(zone.Internal);
break;
case AuthZoneType.Secondary:
case AuthZoneType.Stub:
jsonWriter.WritePropertyName("expiry");
jsonWriter.WriteValue(zone.Expiry.ToLocalTime().ToString("dd MMM yyyy HH:mm:ss"));
jsonWriter.WritePropertyName("isExpired");
jsonWriter.WriteValue(zone.IsExpired);
break;
}
jsonWriter.WritePropertyName("disabled");
jsonWriter.WriteValue(zone.Disabled);
jsonWriter.WriteEndObject();
}
jsonWriter.WriteEndArray();
}
private async Task CreateZoneAsync(HttpListenerRequest request, JsonTextWriter jsonWriter)
{
string domain = request.QueryString["domain"];
if (string.IsNullOrEmpty(domain))
throw new 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.Contains("/"))
{
string[] parts = domain.Split('/');
if ((parts.Length == 2) && IPAddress.TryParse(parts[0], out ipAddress) && int.TryParse(parts[1], out int subnetMaskWidth))
domain = Zone.GetReverseZone(ipAddress, subnetMaskWidth);
}
else if (domain.EndsWith("."))
{
domain = domain.Substring(0, domain.Length - 1);
}
AuthZoneType type = AuthZoneType.Primary;
string strType = request.QueryString["type"];
if (!string.IsNullOrEmpty(strType))
type = (AuthZoneType)Enum.Parse(typeof(AuthZoneType), strType, true);
switch (type)
{
case AuthZoneType.Primary:
if (_dnsServer.AuthZoneManager.CreatePrimaryZone(domain, _dnsServer.ServerDomain, false) == null)
throw new WebServiceException("Zone already exists: " + domain);
_log.Write(GetRequestRemoteEndPoint(request), "[" + GetSession(request).Username + "] Authoritative primary zone was created: " + domain);
_dnsServer.AuthZoneManager.SaveZoneFile(domain);
break;
case AuthZoneType.Secondary:
{
string strPrimaryNameServerAddresses = request.QueryString["primaryNameServerAddresses"];
if (string.IsNullOrEmpty(strPrimaryNameServerAddresses))
strPrimaryNameServerAddresses = null;
if (await _dnsServer.AuthZoneManager.CreateSecondaryZoneAsync(domain, strPrimaryNameServerAddresses) == null)
throw new WebServiceException("Zone already exists: " + domain);
_log.Write(GetRequestRemoteEndPoint(request), "[" + GetSession(request).Username + "] Authoritative secondary zone was created: " + domain);
_dnsServer.AuthZoneManager.SaveZoneFile(domain);
}
break;
case AuthZoneType.Stub:
{
string strPrimaryNameServerAddresses = request.QueryString["primaryNameServerAddresses"];
if (string.IsNullOrEmpty(strPrimaryNameServerAddresses))
strPrimaryNameServerAddresses = null;
if (await _dnsServer.AuthZoneManager.CreateStubZoneAsync(domain, strPrimaryNameServerAddresses) == null)
throw new WebServiceException("Zone already exists: " + domain);
_log.Write(GetRequestRemoteEndPoint(request), "[" + GetSession(request).Username + "] Stub zone was created: " + domain);
_dnsServer.AuthZoneManager.SaveZoneFile(domain);
}
break;
case AuthZoneType.Forwarder:
{
DnsTransportProtocol forwarderProtocol = DnsTransportProtocol.Udp;
string strForwarderProtocol = request.QueryString["protocol"];
if (!string.IsNullOrEmpty(strForwarderProtocol))
forwarderProtocol = (DnsTransportProtocol)Enum.Parse(typeof(DnsTransportProtocol), strForwarderProtocol, true);
string strForwarder = request.QueryString["forwarder"];
if (string.IsNullOrEmpty(strForwarder))
throw new WebServiceException("Parameter 'forwarder' missing.");
if (_dnsServer.AuthZoneManager.CreateForwarderZone(domain, forwarderProtocol, strForwarder) == null)
throw new WebServiceException("Zone already exists: " + domain);
_log.Write(GetRequestRemoteEndPoint(request), "[" + GetSession(request).Username + "] Forwarder zone was created: " + domain);
_dnsServer.AuthZoneManager.SaveZoneFile(domain);
}
break;
default:
throw new NotSupportedException("Zone type not supported.");
}
jsonWriter.WritePropertyName("domain");
jsonWriter.WriteValue(string.IsNullOrEmpty(domain) ? "." : domain);
}
private void DeleteZone(HttpListenerRequest request)
{
string domain = request.QueryString["domain"];
if (string.IsNullOrEmpty(domain))
throw new WebServiceException("Parameter 'domain' missing.");
if (domain.EndsWith("."))
domain = domain.Substring(0, domain.Length - 1);
AuthZoneInfo zoneInfo = _dnsServer.AuthZoneManager.GetAuthZoneInfo(domain);
if (zoneInfo == null)
throw new WebServiceException("Zone '" + domain + "' was not found.");
if (zoneInfo.Internal)
throw new WebServiceException("Access was denied to manage internal DNS Server zone.");
if (!_dnsServer.AuthZoneManager.DeleteZone(domain))
throw new WebServiceException("Zone '" + domain + "' was not found.");
_log.Write(GetRequestRemoteEndPoint(request), "[" + GetSession(request).Username + "] " + zoneInfo.Type.ToString() + " zone was deleted: " + domain);
_dnsServer.AuthZoneManager.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);
AuthZoneInfo zoneInfo = _dnsServer.AuthZoneManager.GetAuthZoneInfo(domain);
if (zoneInfo == null)
throw new WebServiceException("Zone '" + domain + "' was not found.");
if (zoneInfo.Internal)
throw new WebServiceException("Access was denied to manage internal DNS Server zone.");
zoneInfo.Disabled = false;
_log.Write(GetRequestRemoteEndPoint(request), "[" + GetSession(request).Username + "] " + zoneInfo.Type.ToString() + " zone was enabled: " + domain);
_dnsServer.AuthZoneManager.SaveZoneFile(zoneInfo.Name);
}
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);
AuthZoneInfo zoneInfo = _dnsServer.AuthZoneManager.GetAuthZoneInfo(domain);
if (zoneInfo == null)
throw new WebServiceException("Zone '" + domain + "' was not found.");
if (zoneInfo.Internal)
throw new WebServiceException("Access was denied to manage internal DNS Server zone.");
zoneInfo.Disabled = true;
_log.Write(GetRequestRemoteEndPoint(request), "[" + GetSession(request).Username + "] " + zoneInfo.Type.ToString() + " zone was disabled: " + domain);
_dnsServer.AuthZoneManager.SaveZoneFile(zoneInfo.Name);
}
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);
AuthZoneInfo zoneInfo = _dnsServer.AuthZoneManager.GetAuthZoneInfo(domain);
if (zoneInfo == null)
throw new WebServiceException("Zone '" + domain + "' was not found.");
if (zoneInfo.Internal)
throw new WebServiceException("Access was denied to manage internal DNS Server zone.");
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:
case DnsResourceRecordType.AAAA:
IPAddress ipAddress = IPAddress.Parse(value);
bool ptr = false;
string strPtr = request.QueryString["ptr"];
if (!string.IsNullOrEmpty(strPtr))
ptr = bool.Parse(strPtr);
if (ptr)
{
string ptrDomain = Zone.GetReverseZone(ipAddress, 32);
AuthZoneInfo reverseZoneInfo = _dnsServer.AuthZoneManager.GetAuthZoneInfo(ptrDomain);
if (reverseZoneInfo == null)
throw new DnsServerException("No reverse zone available to add PTR record.");
if (reverseZoneInfo.Internal)
throw new DnsServerException("Reverse zone '" + reverseZoneInfo.Name + "' is an internal zone.");
if (reverseZoneInfo.Type != AuthZoneType.Primary)
throw new DnsServerException("Reverse zone '" + reverseZoneInfo.Name + "' is not a primary zone.");
_dnsServer.AuthZoneManager.SetRecords(ptrDomain, DnsResourceRecordType.PTR, ttl, new DnsPTRRecord[] { new DnsPTRRecord(domain) });
_dnsServer.AuthZoneManager.SaveZoneFile(reverseZoneInfo.Name);
}
if (type == DnsResourceRecordType.A)
_dnsServer.AuthZoneManager.AddRecord(domain, type, ttl, new DnsARecord(ipAddress));
else
_dnsServer.AuthZoneManager.AddRecord(domain, type, ttl, new DnsAAAARecord(ipAddress));
break;
case DnsResourceRecordType.MX:
{
string preference = request.QueryString["preference"];
if (string.IsNullOrEmpty(preference))
throw new WebServiceException("Parameter 'preference' missing.");
_dnsServer.AuthZoneManager.AddRecord(domain, type, ttl, new DnsMXRecord(ushort.Parse(preference), value));
}
break;
case DnsResourceRecordType.TXT:
_dnsServer.AuthZoneManager.AddRecord(domain, type, ttl, new DnsTXTRecord(value));
break;
case DnsResourceRecordType.NS:
{
string glueAddresses = request.QueryString["glue"];
if (string.IsNullOrEmpty(glueAddresses))
glueAddresses = null;
DnsResourceRecord nsRecord = new DnsResourceRecord(domain, type, DnsClass.IN, ttl, new DnsNSRecord(value));
if (glueAddresses != null)
nsRecord.SetGlueRecords(glueAddresses);
_dnsServer.AuthZoneManager.AddRecord(nsRecord);
}
break;
case DnsResourceRecordType.PTR:
_dnsServer.AuthZoneManager.SetRecords(domain, type, ttl, new DnsResourceRecordData[] { new DnsPTRRecord(value) });
break;
case DnsResourceRecordType.CNAME:
_dnsServer.AuthZoneManager.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.AuthZoneManager.AddRecord(domain, type, ttl, new DnsSRVRecord(ushort.Parse(priority), ushort.Parse(weight), ushort.Parse(port), value));
}
break;
case DnsResourceRecordType.CAA:
{
string flags = request.QueryString["flags"];
if (string.IsNullOrEmpty(flags))
throw new WebServiceException("Parameter 'flags' missing.");
string tag = request.QueryString["tag"];
if (string.IsNullOrEmpty(tag))
throw new WebServiceException("Parameter 'tag' missing.");
_dnsServer.AuthZoneManager.AddRecord(domain, type, ttl, new DnsCAARecord(byte.Parse(flags), tag, value));
}
break;
case DnsResourceRecordType.ANAME:
_dnsServer.AuthZoneManager.SetRecords(domain, type, ttl, new DnsResourceRecordData[] { new DnsANAMERecord(value) });
break;
case DnsResourceRecordType.FWD:
{
string protocol = request.QueryString["protocol"];
if (string.IsNullOrEmpty(protocol))
protocol = "Udp";
_dnsServer.AuthZoneManager.AddRecord(domain, type, 0, new DnsForwarderRecord((DnsTransportProtocol)Enum.Parse(typeof(DnsTransportProtocol), protocol, true), 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 + ";}");
_dnsServer.AuthZoneManager.SaveZoneFile(zoneInfo.Name);
}
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);
AuthZoneInfo zoneInfo = _dnsServer.AuthZoneManager.GetAuthZoneInfo(domain);
if (zoneInfo == null)
throw new WebServiceException("Zone '" + domain + "' was not found.");
jsonWriter.WritePropertyName("zone");
jsonWriter.WriteStartObject();
jsonWriter.WritePropertyName("name");
jsonWriter.WriteValue(zoneInfo.Name);
jsonWriter.WritePropertyName("type");
jsonWriter.WriteValue(zoneInfo.Type.ToString());
switch (zoneInfo.Type)
{
case AuthZoneType.Primary:
jsonWriter.WritePropertyName("internal");
jsonWriter.WriteValue(zoneInfo.Internal);
break;
case AuthZoneType.Secondary:
case AuthZoneType.Stub:
jsonWriter.WritePropertyName("expiry");
jsonWriter.WriteValue(zoneInfo.Expiry.ToLocalTime().ToString("dd MMM yyyy HH:mm:ss"));
jsonWriter.WritePropertyName("isExpired");
jsonWriter.WriteValue(zoneInfo.IsExpired);
break;
}
jsonWriter.WritePropertyName("disabled");
jsonWriter.WriteValue(zoneInfo.Disabled);
jsonWriter.WriteEndObject();
List records = _dnsServer.AuthZoneManager.ListAllRecords(domain);
WriteRecordsAsJson(records, jsonWriter, true);
}
private static void WriteRecordsAsJson(List records, JsonTextWriter jsonWriter, bool authoritativeZoneRecords)
{
if (records == null)
{
jsonWriter.WritePropertyName("records");
jsonWriter.WriteStartArray();
jsonWriter.WriteEndArray();
return;
}
records.Sort();
Dictionary>> groupedByDomainRecords = DnsResourceRecord.GroupRecords(records);
jsonWriter.WritePropertyName("records");
jsonWriter.WriteStartArray();
foreach (KeyValuePair>> groupedByTypeRecords in groupedByDomainRecords)
{
foreach (KeyValuePair> groupedRecords in groupedByTypeRecords.Value)
{
foreach (DnsResourceRecord record in groupedRecords.Value)
{
jsonWriter.WriteStartObject();
if (authoritativeZoneRecords)
{
jsonWriter.WritePropertyName("disabled");
jsonWriter.WriteValue(record.IsDisabled());
}
jsonWriter.WritePropertyName("name");
jsonWriter.WriteValue(record.Name);
jsonWriter.WritePropertyName("type");
jsonWriter.WriteValue(record.Type.ToString());
jsonWriter.WritePropertyName("ttl");
if (authoritativeZoneRecords)
jsonWriter.WriteValue(record.TtlValue);
else
jsonWriter.WriteValue(record.TTL);
jsonWriter.WritePropertyName("rData");
jsonWriter.WriteStartObject();
switch (record.Type)
{
case DnsResourceRecordType.A:
{
DnsARecord rdata = (record.RDATA as DnsARecord);
if (rdata != null)
{
jsonWriter.WritePropertyName("value");
jsonWriter.WriteValue(rdata.IPAddress);
}
}
break;
case DnsResourceRecordType.AAAA:
{
DnsAAAARecord rdata = (record.RDATA as DnsAAAARecord);
if (rdata != null)
{
jsonWriter.WritePropertyName("value");
jsonWriter.WriteValue(rdata.IPAddress);
}
}
break;
case DnsResourceRecordType.SOA:
{
DnsSOARecord rdata = record.RDATA as DnsSOARecord;
if (rdata != null)
{
jsonWriter.WritePropertyName("primaryNameServer");
jsonWriter.WriteValue(rdata.PrimaryNameServer);
jsonWriter.WritePropertyName("responsiblePerson");
jsonWriter.WriteValue(rdata.ResponsiblePerson);
jsonWriter.WritePropertyName("serial");
jsonWriter.WriteValue(rdata.Serial);
jsonWriter.WritePropertyName("refresh");
jsonWriter.WriteValue(rdata.Refresh);
jsonWriter.WritePropertyName("retry");
jsonWriter.WriteValue(rdata.Retry);
jsonWriter.WritePropertyName("expire");
jsonWriter.WriteValue(rdata.Expire);
jsonWriter.WritePropertyName("minimum");
jsonWriter.WriteValue(rdata.Minimum);
}
IReadOnlyList glueRecords = record.GetGlueRecords();
if (glueRecords.Count > 0)
{
string primaryAddresses = null;
foreach (DnsResourceRecord glueRecord in glueRecords)
{
if (primaryAddresses == null)
primaryAddresses = glueRecord.RDATA.ToString();
else
primaryAddresses = primaryAddresses + ", " + glueRecord.RDATA.ToString();
}
jsonWriter.WritePropertyName("primaryAddresses");
jsonWriter.WriteValue(primaryAddresses);
}
}
break;
case DnsResourceRecordType.PTR:
{
DnsPTRRecord rdata = record.RDATA as DnsPTRRecord;
if (rdata != null)
{
jsonWriter.WritePropertyName("value");
jsonWriter.WriteValue(rdata.Domain);
}
}
break;
case DnsResourceRecordType.MX:
{
DnsMXRecord rdata = record.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 = record.RDATA as DnsTXTRecord;
if (rdata != null)
{
jsonWriter.WritePropertyName("value");
jsonWriter.WriteValue(rdata.Text);
}
}
break;
case DnsResourceRecordType.NS:
{
DnsNSRecord rdata = record.RDATA as DnsNSRecord;
if (rdata != null)
{
jsonWriter.WritePropertyName("value");
jsonWriter.WriteValue(rdata.NameServer);
}
IReadOnlyList glueRecords = record.GetGlueRecords();
if (glueRecords.Count > 0)
{
string glue = null;
foreach (DnsResourceRecord glueRecord in glueRecords)
{
if (glue == null)
glue = glueRecord.RDATA.ToString();
else
glue = glue + ", " + glueRecord.RDATA.ToString();
}
jsonWriter.WritePropertyName("glue");
jsonWriter.WriteValue(glue);
}
}
break;
case DnsResourceRecordType.CNAME:
{
DnsCNAMERecord rdata = record.RDATA as DnsCNAMERecord;
if (rdata != null)
{
jsonWriter.WritePropertyName("value");
jsonWriter.WriteValue(rdata.Domain);
}
}
break;
case DnsResourceRecordType.SRV:
{
DnsSRVRecord rdata = record.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;
case DnsResourceRecordType.CAA:
{
DnsCAARecord rdata = record.RDATA as DnsCAARecord;
if (rdata != null)
{
jsonWriter.WritePropertyName("flags");
jsonWriter.WriteValue(rdata.Flags);
jsonWriter.WritePropertyName("tag");
jsonWriter.WriteValue(rdata.Tag);
jsonWriter.WritePropertyName("value");
jsonWriter.WriteValue(rdata.Value);
}
}
break;
case DnsResourceRecordType.ANAME:
{
DnsANAMERecord rdata = record.RDATA as DnsANAMERecord;
if (rdata != null)
{
jsonWriter.WritePropertyName("value");
jsonWriter.WriteValue(rdata.Domain);
}
}
break;
case DnsResourceRecordType.FWD:
{
DnsForwarderRecord rdata = record.RDATA as DnsForwarderRecord;
if (rdata != null)
{
jsonWriter.WritePropertyName("protocol");
jsonWriter.WriteValue(rdata.Protocol.ToString());
jsonWriter.WritePropertyName("value");
jsonWriter.WriteValue(rdata.Forwarder);
}
}
break;
default:
{
jsonWriter.WritePropertyName("value");
using (MemoryStream mS = new MemoryStream())
{
record.RDATA.WriteTo(mS, new List());
jsonWriter.WriteValue(Convert.ToBase64String(mS.ToArray()));
}
}
break;
}
jsonWriter.WriteEndObject();
jsonWriter.WriteEndObject();
}
}
}
jsonWriter.WriteEndArray();
}
private void DeleteRecord(HttpListenerRequest request)
{
string domain = request.QueryString["domain"];
if (string.IsNullOrEmpty(domain))
throw new WebServiceException("Parameter 'domain' missing.");
if (domain.EndsWith("."))
domain = domain.Substring(0, domain.Length - 1);
AuthZoneInfo zoneInfo = _dnsServer.AuthZoneManager.GetAuthZoneInfo(domain);
if (zoneInfo == null)
throw new WebServiceException("Zone '" + domain + "' was not found.");
if (zoneInfo.Internal)
throw new WebServiceException("Access was denied to manage internal DNS Server zone.");
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.");
switch (type)
{
case DnsResourceRecordType.A:
_dnsServer.AuthZoneManager.DeleteRecord(domain, type, new DnsARecord(IPAddress.Parse(value)));
break;
case DnsResourceRecordType.AAAA:
_dnsServer.AuthZoneManager.DeleteRecord(domain, type, new DnsAAAARecord(IPAddress.Parse(value)));
break;
case DnsResourceRecordType.MX:
_dnsServer.AuthZoneManager.DeleteRecord(domain, type, new DnsMXRecord(0, value));
break;
case DnsResourceRecordType.TXT:
_dnsServer.AuthZoneManager.DeleteRecord(domain, type, new DnsTXTRecord(value));
break;
case DnsResourceRecordType.NS:
_dnsServer.AuthZoneManager.DeleteRecord(domain, type, new DnsNSRecord(value));
break;
case DnsResourceRecordType.CNAME:
case DnsResourceRecordType.PTR:
case DnsResourceRecordType.ANAME:
_dnsServer.AuthZoneManager.DeleteRecords(domain, type);
break;
case DnsResourceRecordType.SRV:
{
string port = request.QueryString["port"];
if (string.IsNullOrEmpty(port))
throw new WebServiceException("Parameter 'port' missing.");
_dnsServer.AuthZoneManager.DeleteRecord(domain, type, new DnsSRVRecord(0, 0, ushort.Parse(port), value));
}
break;
case DnsResourceRecordType.CAA:
{
string flags = request.QueryString["flags"];
if (string.IsNullOrEmpty(flags))
throw new WebServiceException("Parameter 'flags' missing.");
string tag = request.QueryString["tag"];
if (string.IsNullOrEmpty(tag))
throw new WebServiceException("Parameter 'tag' missing.");
_dnsServer.AuthZoneManager.DeleteRecord(domain, type, new DnsCAARecord(byte.Parse(flags), tag, value));
}
break;
case DnsResourceRecordType.FWD:
{
string strProtocol = request.QueryString["protocol"];
if (string.IsNullOrEmpty(strProtocol))
strProtocol = "Udp";
_dnsServer.AuthZoneManager.DeleteRecord(domain, type, new DnsForwarderRecord((DnsTransportProtocol)Enum.Parse(typeof(DnsTransportProtocol), strProtocol, true), value));
}
break;
default:
throw new 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 + ";}");
_dnsServer.AuthZoneManager.SaveZoneFile(zoneInfo.Name);
}
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);
AuthZoneInfo zoneInfo = _dnsServer.AuthZoneManager.GetAuthZoneInfo(domain);
if (zoneInfo == null)
throw new WebServiceException("Zone '" + domain + "' was not found.");
if (zoneInfo.Internal)
throw new WebServiceException("Access was denied to manage internal DNS Server zone.");
string newDomain = request.QueryString["newDomain"];
if (string.IsNullOrEmpty(newDomain))
newDomain = domain;
if (newDomain.EndsWith("."))
newDomain = newDomain.Substring(0, newDomain.Length - 1);
uint ttl;
string strTtl = request.QueryString["ttl"];
if (string.IsNullOrEmpty(strTtl))
ttl = 3600;
else
ttl = uint.Parse(strTtl);
string value = request.QueryString["value"];
string newValue = request.QueryString["newValue"];
if (string.IsNullOrEmpty(newValue))
newValue = value;
bool disable = false;
string strDisable = request.QueryString["disable"];
if (!string.IsNullOrEmpty(strDisable))
disable = bool.Parse(strDisable);
switch (type)
{
case DnsResourceRecordType.A:
case DnsResourceRecordType.AAAA:
{
IPAddress oldIpAddress = IPAddress.Parse(value);
IPAddress newIpAddress = IPAddress.Parse(newValue);
bool ptr = false;
string strPtr = request.QueryString["ptr"];
if (!string.IsNullOrEmpty(strPtr))
ptr = bool.Parse(strPtr);
if (ptr)
{
//delete old PTR record if any
_dnsServer.AuthZoneManager.DeleteRecords(Zone.GetReverseZone(oldIpAddress, 32), DnsResourceRecordType.PTR);
//add new PTR record
string ptrDomain = Zone.GetReverseZone(newIpAddress, 32);
AuthZoneInfo reverseZoneInfo = _dnsServer.AuthZoneManager.GetAuthZoneInfo(ptrDomain);
if (reverseZoneInfo == null)
throw new DnsServerException("No reverse zone available to add PTR record.");
if (reverseZoneInfo.Internal)
throw new DnsServerException("Reverse zone '" + reverseZoneInfo.Name + "' is an internal zone.");
if (reverseZoneInfo.Type != AuthZoneType.Primary)
throw new DnsServerException("Reverse zone '" + reverseZoneInfo.Name + "' is not a primary zone.");
_dnsServer.AuthZoneManager.SetRecords(ptrDomain, DnsResourceRecordType.PTR, ttl, new DnsPTRRecord[] { new DnsPTRRecord(domain) });
_dnsServer.AuthZoneManager.SaveZoneFile(reverseZoneInfo.Name);
}
DnsResourceRecord oldRecord;
DnsResourceRecord newRecord;
if (type == DnsResourceRecordType.A)
{
oldRecord = new DnsResourceRecord(domain, type, DnsClass.IN, 0, new DnsARecord(oldIpAddress));
newRecord = new DnsResourceRecord(newDomain, type, DnsClass.IN, ttl, new DnsARecord(newIpAddress));
}
else
{
oldRecord = new DnsResourceRecord(domain, type, DnsClass.IN, 0, new DnsAAAARecord(oldIpAddress));
newRecord = new DnsResourceRecord(newDomain, type, DnsClass.IN, ttl, new DnsAAAARecord(newIpAddress));
}
if (disable)
newRecord.Disable();
_dnsServer.AuthZoneManager.UpdateRecord(oldRecord, newRecord);
}
break;
case DnsResourceRecordType.MX:
{
string preference = request.QueryString["preference"];
if (string.IsNullOrEmpty(preference))
preference = "1";
DnsResourceRecord oldRecord = new DnsResourceRecord(domain, type, DnsClass.IN, 0, new DnsMXRecord(0, value));
DnsResourceRecord newRecord = new DnsResourceRecord(newDomain, type, DnsClass.IN, ttl, new DnsMXRecord(ushort.Parse(preference), newValue));
if (disable)
newRecord.Disable();
_dnsServer.AuthZoneManager.UpdateRecord(oldRecord, newRecord);
}
break;
case DnsResourceRecordType.TXT:
{
DnsResourceRecord oldRecord = new DnsResourceRecord(domain, type, DnsClass.IN, 0, new DnsTXTRecord(value));
DnsResourceRecord newRecord = new DnsResourceRecord(newDomain, type, DnsClass.IN, ttl, new DnsTXTRecord(newValue));
if (disable)
newRecord.Disable();
_dnsServer.AuthZoneManager.UpdateRecord(oldRecord, newRecord);
}
break;
case DnsResourceRecordType.NS:
{
DnsResourceRecord oldRecord = new DnsResourceRecord(domain, type, DnsClass.IN, 0, new DnsNSRecord(value));
DnsResourceRecord newRecord = new DnsResourceRecord(newDomain, type, DnsClass.IN, ttl, new DnsNSRecord(newValue));
if (disable)
newRecord.Disable();
string glueAddresses = request.QueryString["glue"];
if (!string.IsNullOrEmpty(glueAddresses))
newRecord.SetGlueRecords(glueAddresses);
_dnsServer.AuthZoneManager.UpdateRecord(oldRecord, newRecord);
}
break;
case DnsResourceRecordType.SOA:
{
string primaryNameServer = request.QueryString["primaryNameServer"];
if (string.IsNullOrEmpty(primaryNameServer))
throw new WebServiceException("Parameter 'primaryNameServer' 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.");
DnsResourceRecord soaRecord = new DnsResourceRecord(domain, type, DnsClass.IN, ttl, new DnsSOARecord(primaryNameServer, responsiblePerson, uint.Parse(serial), uint.Parse(refresh), uint.Parse(retry), uint.Parse(expire), uint.Parse(minimum)));
string primaryAddresses = request.QueryString["primaryAddresses"];
if (!string.IsNullOrEmpty(primaryAddresses))
soaRecord.SetGlueRecords(primaryAddresses);
_dnsServer.AuthZoneManager.SetRecord(soaRecord);
}
break;
case DnsResourceRecordType.PTR:
{
DnsResourceRecord oldRecord = new DnsResourceRecord(domain, type, DnsClass.IN, 0, new DnsPTRRecord(value));
DnsResourceRecord newRecord = new DnsResourceRecord(newDomain, type, DnsClass.IN, ttl, new DnsPTRRecord(newValue));
if (disable)
newRecord.Disable();
_dnsServer.AuthZoneManager.UpdateRecord(oldRecord, newRecord);
}
break;
case DnsResourceRecordType.CNAME:
{
DnsResourceRecord oldRecord = new DnsResourceRecord(domain, type, DnsClass.IN, 0, new DnsCNAMERecord(value));
DnsResourceRecord newRecord = new DnsResourceRecord(newDomain, type, DnsClass.IN, ttl, new DnsCNAMERecord(newValue));
if (disable)
newRecord.Disable();
_dnsServer.AuthZoneManager.UpdateRecord(oldRecord, newRecord);
}
break;
case DnsResourceRecordType.SRV:
{
string port = request.QueryString["port"];
if (string.IsNullOrEmpty(port))
throw new WebServiceException("Parameter 'port' 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 newPort = request.QueryString["newPort"];
if (string.IsNullOrEmpty(newPort))
newPort = port;
DnsResourceRecord oldRecord = new DnsResourceRecord(domain, type, DnsClass.IN, 0, new DnsSRVRecord(0, 0, ushort.Parse(port), value));
DnsResourceRecord newRecord = new DnsResourceRecord(newDomain, type, DnsClass.IN, ttl, new DnsSRVRecord(ushort.Parse(priority), ushort.Parse(weight), ushort.Parse(newPort), newValue));
if (disable)
newRecord.Disable();
_dnsServer.AuthZoneManager.UpdateRecord(oldRecord, newRecord);
}
break;
case DnsResourceRecordType.CAA:
{
string flags = request.QueryString["flags"];
if (string.IsNullOrEmpty(flags))
throw new WebServiceException("Parameter 'flags' missing.");
string tag = request.QueryString["tag"];
if (string.IsNullOrEmpty(tag))
throw new WebServiceException("Parameter 'tag' missing.");
string newFlags = request.QueryString["newFlags"];
if (string.IsNullOrEmpty(newFlags))
newFlags = flags;
string newTag = request.QueryString["newTag"];
if (string.IsNullOrEmpty(newTag))
newTag = tag;
DnsResourceRecord oldRecord = new DnsResourceRecord(domain, type, DnsClass.IN, 0, new DnsCAARecord(byte.Parse(flags), tag, value));
DnsResourceRecord newRecord = new DnsResourceRecord(newDomain, type, DnsClass.IN, ttl, new DnsCAARecord(byte.Parse(newFlags), newTag, newValue));
if (disable)
newRecord.Disable();
_dnsServer.AuthZoneManager.UpdateRecord(oldRecord, newRecord);
}
break;
case DnsResourceRecordType.ANAME:
{
DnsResourceRecord oldRecord = new DnsResourceRecord(domain, type, DnsClass.IN, 0, new DnsANAMERecord(value));
DnsResourceRecord newRecord = new DnsResourceRecord(newDomain, type, DnsClass.IN, ttl, new DnsANAMERecord(newValue));
if (disable)
newRecord.Disable();
_dnsServer.AuthZoneManager.UpdateRecord(oldRecord, newRecord);
}
break;
case DnsResourceRecordType.FWD:
{
string strProtocol = request.QueryString["protocol"];
if (string.IsNullOrEmpty(strProtocol))
strProtocol = "Udp";
DnsTransportProtocol protocol = (DnsTransportProtocol)Enum.Parse(typeof(DnsTransportProtocol), strProtocol, true);
DnsResourceRecord oldRecord = new DnsResourceRecord(domain, type, DnsClass.IN, 0, new DnsForwarderRecord(protocol, value));
DnsResourceRecord newRecord = new DnsResourceRecord(newDomain, type, DnsClass.IN, ttl, new DnsForwarderRecord(protocol, newValue));
if (disable)
newRecord.Disable();
_dnsServer.AuthZoneManager.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: " + domain + "; domain: " + newDomain + "; type: " + type + "; oldValue: " + value + "; value: " + newValue + "; ttl: " + ttl + "; disabled: " + disable + ";}");
_dnsServer.AuthZoneManager.SaveZoneFile(zoneInfo.Name);
}
private async Task 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.");
domain = domain.Trim();
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.Equals("recursive-resolver", StringComparison.OrdinalIgnoreCase))
{
if (type == DnsResourceRecordType.AXFR)
throw new DnsServerException("Cannot do zone transfer (AXFR) for 'recursive-resolver'.");
DnsQuestionRecord question;
if ((type == DnsResourceRecordType.PTR) && IPAddress.TryParse(domain, out IPAddress address))
question = new DnsQuestionRecord(address, DnsClass.IN);
else
question = new DnsQuestionRecord(domain, type, DnsClass.IN);
dnsResponse = await DnsClient.RecursiveResolveAsync(question, null, null, proxy, preferIPv6, RETRIES, TIMEOUT);
}
else
{
if (type == DnsResourceRecordType.AXFR)
protocol = DnsTransportProtocol.Tcp;
NameServerAddress nameServer;
if (server.Equals("this-server", StringComparison.OrdinalIgnoreCase))
{
switch (protocol)
{
case DnsTransportProtocol.Udp:
nameServer = _dnsServer.ThisServer;
break;
case DnsTransportProtocol.Tcp:
nameServer = new NameServerAddress(_dnsServer.ThisServer, DnsTransportProtocol.Tcp);
break;
case DnsTransportProtocol.Tls:
throw new DnsServerException("Cannot use DNS-over-TLS protocol for 'this-server'. Please use the TLS certificate domain name as the server.");
case DnsTransportProtocol.Https:
throw new DnsServerException("Cannot use DNS-over-HTTPS protocol for 'this-server'. Please use the TLS certificate domain name with a url as the server.");
case DnsTransportProtocol.HttpsJson:
throw new DnsServerException("Cannot use DNS-over-HTTPS (JSON) protocol for 'this-server'. Please use the TLS certificate domain name with a url as the server.");
default:
throw new InvalidOperationException();
}
proxy = null; //no proxy required for this server
}
else
{
if ((protocol == DnsTransportProtocol.Tls) && !server.Contains(":853"))
server += ":853";
nameServer = new NameServerAddress(server, protocol);
if (nameServer.IPEndPoint == null)
{
if (proxy == null)
{
if (_dnsServer.AllowRecursion)
await nameServer.ResolveIPAddressAsync(new NameServerAddress[] { _dnsServer.ThisServer }, proxy, preferIPv6, RETRIES, TIMEOUT);
else
await nameServer.RecursiveResolveIPAddressAsync(_dnsServer.DnsCache, proxy, preferIPv6, RETRIES, TIMEOUT);
}
}
else if (protocol != DnsTransportProtocol.Tls)
{
try
{
if (_dnsServer.AllowRecursion)
await nameServer.ResolveDomainNameAsync(new NameServerAddress[] { _dnsServer.ThisServer }, proxy, preferIPv6, RETRIES, TIMEOUT);
else
await nameServer.RecursiveResolveDomainNameAsync(_dnsServer.DnsCache, proxy, preferIPv6, RETRIES, TIMEOUT);
}
catch
{ }
}
}
dnsResponse = await new DnsClient(nameServer) { Proxy = proxy, PreferIPv6 = preferIPv6, Retries = RETRIES, Timeout = TIMEOUT }.ResolveAsync(domain, type);
}
if (importRecords)
{
AuthZoneInfo zoneInfo = _dnsServer.AuthZoneManager.GetAuthZoneInfo(domain);
if (zoneInfo == null)
{
zoneInfo = _dnsServer.AuthZoneManager.CreatePrimaryZone(domain, _dnsServer.ServerDomain, false);
if (zoneInfo == null)
throw new DnsServerException("Cannot import records: failed to create primary zone.");
}
else
{
switch (zoneInfo.Type)
{
case AuthZoneType.Primary:
case AuthZoneType.Forwarder:
break;
default:
throw new DnsServerException("Cannot import records: import zone must be of primary or forwarder type.");
}
}
if (type == DnsResourceRecordType.AXFR)
{
bool dontRemoveRecords = zoneInfo.Type == AuthZoneType.Forwarder;
_dnsServer.AuthZoneManager.SyncRecords(domain, dnsResponse.Answer, null, dontRemoveRecords);
}
else
{
List syncRecords = new List();
foreach (DnsResourceRecord record in dnsResponse.Answer)
{
if (record.Name.Equals(domain, StringComparison.OrdinalIgnoreCase))
{
record.RemoveExpiry();
syncRecords.Add(record);
}
}
_dnsServer.AuthZoneManager.SyncRecords(domain, syncRecords, null, true);
}
_log.Write(GetRequestRemoteEndPoint(request), "[" + GetSession(request).Username + "] DNS Client imported record(s) for authoritative zone {server: " + server + "; domain: " + domain + "; type: " + type + ";}");
_dnsServer.AuthZoneManager.SaveZoneFile(zoneInfo.Name);
}
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;
//sort by name
Scope[] scopesArray = new Scope[scopes.Count];
scopes.CopyTo(scopesArray, 0);
Array.Sort(scopesArray);
jsonWriter.WritePropertyName("leases");
jsonWriter.WriteStartArray();
foreach (Scope scope in scopesArray)
{
ICollection leases = scope.Leases;
//sort by address
Lease[] leasesArray = new Lease[leases.Count];
leases.CopyTo(leasesArray, 0);
Array.Sort(leasesArray);
foreach (Lease lease in leasesArray)
{
jsonWriter.WriteStartObject();
jsonWriter.WritePropertyName("scope");
jsonWriter.WriteValue(scope.Name);
jsonWriter.WritePropertyName("type");
jsonWriter.WriteValue(lease.Type.ToString());
jsonWriter.WritePropertyName("hardwareAddress");
jsonWriter.WriteValue(BitConverter.ToString(lease.HardwareAddress));
jsonWriter.WritePropertyName("address");
jsonWriter.WriteValue(lease.Address.ToString());
jsonWriter.WritePropertyName("hostName");
jsonWriter.WriteValue(lease.HostName);
jsonWriter.WritePropertyName("leaseObtained");
jsonWriter.WriteValue(lease.LeaseObtained.ToLocalTime().ToString());
jsonWriter.WritePropertyName("leaseExpires");
jsonWriter.WriteValue(lease.LeaseExpires.ToLocalTime().ToString());
jsonWriter.WriteEndObject();
}
}
jsonWriter.WriteEndArray();
}
private void ListDhcpScopes(JsonTextWriter jsonWriter)
{
ICollection scopes = _dhcpServer.Scopes;
//sort by name
Scope[] scopesArray = new Scope[scopes.Count];
scopes.CopyTo(scopesArray, 0);
Array.Sort(scopesArray);
jsonWriter.WritePropertyName("scopes");
jsonWriter.WriteStartArray();
foreach (Scope scope in scopesArray)
{
jsonWriter.WriteStartObject();
jsonWriter.WritePropertyName("name");
jsonWriter.WriteValue(scope.Name);
jsonWriter.WritePropertyName("enabled");
jsonWriter.WriteValue(scope.Enabled);
jsonWriter.WritePropertyName("startingAddress");
jsonWriter.WriteValue(scope.StartingAddress.ToString());
jsonWriter.WritePropertyName("endingAddress");
jsonWriter.WriteValue(scope.EndingAddress.ToString());
jsonWriter.WritePropertyName("subnetMask");
jsonWriter.WriteValue(scope.SubnetMask.ToString());
jsonWriter.WritePropertyName("networkAddress");
jsonWriter.WriteValue(scope.NetworkAddress.ToString());
jsonWriter.WritePropertyName("broadcastAddress");
jsonWriter.WriteValue(scope.BroadcastAddress.ToString());
if (scope.InterfaceAddress != null)
{
jsonWriter.WritePropertyName("interfaceAddress");
jsonWriter.WriteValue(scope.InterfaceAddress.ToString());
}
jsonWriter.WriteEndObject();
}
jsonWriter.WriteEndArray();
}
private void GetDhcpScope(HttpListenerRequest request, JsonTextWriter jsonWriter)
{
string scopeName = request.QueryString["name"];
if (string.IsNullOrEmpty(scopeName))
throw new 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("leaseTimeDays");
jsonWriter.WriteValue(scope.LeaseTimeDays);
jsonWriter.WritePropertyName("leaseTimeHours");
jsonWriter.WriteValue(scope.LeaseTimeHours);
jsonWriter.WritePropertyName("leaseTimeMinutes");
jsonWriter.WriteValue(scope.LeaseTimeMinutes);
jsonWriter.WritePropertyName("offerDelayTime");
jsonWriter.WriteValue(scope.OfferDelayTime);
if (!string.IsNullOrEmpty(scope.DomainName))
{
jsonWriter.WritePropertyName("domainName");
jsonWriter.WriteValue(scope.DomainName);
}
jsonWriter.WritePropertyName("dnsTtl");
jsonWriter.WriteValue(scope.DnsTtl);
if (scope.RouterAddress != null)
{
jsonWriter.WritePropertyName("routerAddress");
jsonWriter.WriteValue(scope.RouterAddress.ToString());
}
jsonWriter.WritePropertyName("useThisDnsServer");
jsonWriter.WriteValue(scope.UseThisDnsServer);
if (scope.DnsServers != null)
{
jsonWriter.WritePropertyName("dnsServers");
jsonWriter.WriteStartArray();
foreach (IPAddress dnsServer in scope.DnsServers)
jsonWriter.WriteValue(dnsServer.ToString());
jsonWriter.WriteEndArray();
}
if (scope.WinsServers != null)
{
jsonWriter.WritePropertyName("winsServers");
jsonWriter.WriteStartArray();
foreach (IPAddress winsServer in scope.WinsServers)
jsonWriter.WriteValue(winsServer.ToString());
jsonWriter.WriteEndArray();
}
if (scope.NtpServers != null)
{
jsonWriter.WritePropertyName("ntpServers");
jsonWriter.WriteStartArray();
foreach (IPAddress ntpServer in scope.NtpServers)
jsonWriter.WriteValue(ntpServer.ToString());
jsonWriter.WriteEndArray();
}
if (scope.StaticRoutes != null)
{
jsonWriter.WritePropertyName("staticRoutes");
jsonWriter.WriteStartArray();
foreach (ClasslessStaticRouteOption.Route route in scope.StaticRoutes)
{
jsonWriter.WriteStartObject();
jsonWriter.WritePropertyName("destination");
jsonWriter.WriteValue(route.Destination.ToString());
jsonWriter.WritePropertyName("subnetMask");
jsonWriter.WriteValue(route.SubnetMask.ToString());
jsonWriter.WritePropertyName("router");
jsonWriter.WriteValue(route.Router.ToString());
jsonWriter.WriteEndObject();
}
jsonWriter.WriteEndArray();
}
if (scope.Exclusions != null)
{
jsonWriter.WritePropertyName("exclusions");
jsonWriter.WriteStartArray();
foreach (Exclusion exclusion in scope.Exclusions)
{
jsonWriter.WriteStartObject();
jsonWriter.WritePropertyName("startingAddress");
jsonWriter.WriteValue(exclusion.StartingAddress.ToString());
jsonWriter.WritePropertyName("endingAddress");
jsonWriter.WriteValue(exclusion.EndingAddress.ToString());
jsonWriter.WriteEndObject();
}
jsonWriter.WriteEndArray();
}
jsonWriter.WritePropertyName("reservedLeases");
jsonWriter.WriteStartArray();
foreach (Lease reservedLease in scope.ReservedLeases)
{
jsonWriter.WriteStartObject();
jsonWriter.WritePropertyName("hostName");
jsonWriter.WriteValue(reservedLease.HostName);
jsonWriter.WritePropertyName("hardwareAddress");
jsonWriter.WriteValue(BitConverter.ToString(reservedLease.HardwareAddress));
jsonWriter.WritePropertyName("address");
jsonWriter.WriteValue(reservedLease.Address.ToString());
jsonWriter.WritePropertyName("comments");
jsonWriter.WriteValue(reservedLease.Comments);
jsonWriter.WriteEndObject();
}
jsonWriter.WriteEndArray();
jsonWriter.WritePropertyName("allowOnlyReservedLeases");
jsonWriter.WriteValue(scope.AllowOnlyReservedLeases);
}
private async Task SetDhcpScopeAsync(HttpListenerRequest request)
{
string scopeName = request.QueryString["name"];
if (string.IsNullOrEmpty(scopeName))
throw new WebServiceException("Parameter 'name' missing.");
string newName = request.QueryString["newName"];
if (!string.IsNullOrEmpty(newName) && !newName.Equals(scopeName))
{
_dhcpServer.RenameScope(scopeName, newName);
_log.Write(GetRequestRemoteEndPoint(request), "[" + GetSession(request).Username + "] DHCP scope was renamed successfully: '" + scopeName + "' to '" + newName + "'");
scopeName = newName;
}
string strStartingAddress = request.QueryString["startingAddress"];
if (string.IsNullOrEmpty(strStartingAddress))
throw new 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;
IPAddress startingAddress = IPAddress.Parse(strStartingAddress);
IPAddress endingAddress = IPAddress.Parse(strEndingAddress);
//validate scope address
foreach (Scope existingScope in _dhcpServer.Scopes)
{
if (existingScope.Equals(scope))
continue;
if (existingScope.IsAddressInRange(startingAddress) || existingScope.IsAddressInRange(endingAddress))
throw new DhcpServerException("Scope with overlapping range already exists: " + existingScope.StartingAddress.ToString() + "-" + existingScope.EndingAddress.ToString());
}
scope.ChangeNetwork(startingAddress, endingAddress, IPAddress.Parse(strSubnetMask));
}
string strLeaseTimeDays = request.QueryString["leaseTimeDays"];
if (!string.IsNullOrEmpty(strLeaseTimeDays))
scope.LeaseTimeDays = ushort.Parse(strLeaseTimeDays);
string strLeaseTimeHours = request.QueryString["leaseTimeHours"];
if (!string.IsNullOrEmpty(strLeaseTimeHours))
scope.LeaseTimeHours = byte.Parse(strLeaseTimeHours);
string strLeaseTimeMinutes = request.QueryString["leaseTimeMinutes"];
if (!string.IsNullOrEmpty(strLeaseTimeMinutes))
scope.LeaseTimeMinutes = byte.Parse(strLeaseTimeMinutes);
string strOfferDelayTime = request.QueryString["offerDelayTime"];
if (!string.IsNullOrEmpty(strOfferDelayTime))
scope.OfferDelayTime = ushort.Parse(strOfferDelayTime);
string strDomainName = request.QueryString["domainName"];
if (strDomainName != null)
scope.DomainName = strDomainName.Length == 0 ? null : strDomainName;
string strDnsTtl = request.QueryString["dnsTtl"];
if (!string.IsNullOrEmpty(strDnsTtl))
scope.DnsTtl = uint.Parse(strDnsTtl);
string strRouterAddress = request.QueryString["routerAddress"];
if (strRouterAddress != null)
scope.RouterAddress = strRouterAddress.Length == 0 ? null : IPAddress.Parse(strRouterAddress);
string strUseThisDnsServer = request.QueryString["useThisDnsServer"];
if (!string.IsNullOrEmpty(strUseThisDnsServer))
scope.UseThisDnsServer = bool.Parse(strUseThisDnsServer);
if (!scope.UseThisDnsServer)
{
string strDnsServers = request.QueryString["dnsServers"];
if (strDnsServers != null)
{
if (strDnsServers.Length == 0)
{
scope.DnsServers = null;
}
else
{
string[] strDnsServerParts = strDnsServers.Split(new char[] { ',' }, StringSplitOptions.RemoveEmptyEntries);
IPAddress[] dnsServers = new IPAddress[strDnsServerParts.Length];
for (int i = 0; i < strDnsServerParts.Length; i++)
dnsServers[i] = IPAddress.Parse(strDnsServerParts[i]);
scope.DnsServers = dnsServers;
}
}
}
string strWinsServers = request.QueryString["winsServers"];
if (strWinsServers != null)
{
if (strWinsServers.Length == 0)
{
scope.WinsServers = null;
}
else
{
string[] strWinsServerParts = strWinsServers.Split(new char[] { ',' }, StringSplitOptions.RemoveEmptyEntries);
IPAddress[] winsServers = new IPAddress[strWinsServerParts.Length];
for (int i = 0; i < strWinsServerParts.Length; i++)
winsServers[i] = IPAddress.Parse(strWinsServerParts[i]);
scope.WinsServers = winsServers;
}
}
string strNtpServers = request.QueryString["ntpServers"];
if (strNtpServers != null)
{
if (strNtpServers.Length == 0)
{
scope.NtpServers = null;
}
else
{
string[] strNtpServerParts = strNtpServers.Split(new char[] { ',' }, StringSplitOptions.RemoveEmptyEntries);
IPAddress[] ntpServers = new IPAddress[strNtpServerParts.Length];
for (int i = 0; i < strNtpServerParts.Length; i++)
ntpServers[i] = IPAddress.Parse(strNtpServerParts[i]);
scope.NtpServers = ntpServers;
}
}
string strStaticRoutes = request.QueryString["staticRoutes"];
if (strStaticRoutes != null)
{
if (strStaticRoutes.Length == 0)
{
scope.StaticRoutes = null;
}
else
{
string[] strStaticRoutesParts = strStaticRoutes.Split(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.Length == 0)
{
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.Length == 0)
{
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(';');
Lease reservedLease = new Lease(LeaseType.Reserved, null, leaseParts[0], IPAddress.Parse(leaseParts[1]), leaseParts[2]);
Lease existingReservedLease = scope.GetReservedLease(reservedLease.ClientIdentifier);
if (existingReservedLease != null)
reservedLease.SetHostName(existingReservedLease.HostName);
reservedLeases[i] = reservedLease;
}
scope.ReservedLeases = reservedLeases;
}
}
string strAllowOnlyReservedLeases = request.QueryString["allowOnlyReservedLeases"];
if (!string.IsNullOrEmpty(strAllowOnlyReservedLeases))
scope.AllowOnlyReservedLeases = bool.Parse(strAllowOnlyReservedLeases);
if (scopeExists)
{
_dhcpServer.SaveScope(scopeName);
_log.Write(GetRequestRemoteEndPoint(request), "[" + GetSession(request).Username + "] DHCP scope was updated successfully: " + scopeName);
}
else
{
await _dhcpServer.AddScopeAsync(scope);
_log.Write(GetRequestRemoteEndPoint(request), "[" + GetSession(request).Username + "] DHCP scope was added successfully: " + scopeName);
}
}
private async Task EnableDhcpScopeAsync(HttpListenerRequest request)
{
string scopeName = request.QueryString["name"];
if (string.IsNullOrEmpty(scopeName))
throw new WebServiceException("Parameter 'name' missing.");
if (!await _dhcpServer.EnableScopeAsync(scopeName))
throw new WebServiceException("Failed to enable DHCP scope, please check logs for details: " + scopeName);
_log.Write(GetRequestRemoteEndPoint(request), "[" + GetSession(request).Username + "] DHCP scope was enabled successfully: " + scopeName);
}
private void DisableDhcpScope(HttpListenerRequest request)
{
string scopeName = request.QueryString["name"];
if (string.IsNullOrEmpty(scopeName))
throw new WebServiceException("Parameter 'name' missing.");
if (!_dhcpServer.DisableScope(scopeName))
throw new WebServiceException("Failed to disable DHCP scope, please check logs for details: " + scopeName);
_log.Write(GetRequestRemoteEndPoint(request), "[" + GetSession(request).Username + "] DHCP scope was disabled successfully: " + scopeName);
}
private void DeleteDhcpScope(HttpListenerRequest request)
{
string scopeName = request.QueryString["name"];
if (string.IsNullOrEmpty(scopeName))
throw new WebServiceException("Parameter 'name' missing.");
_dhcpServer.DeleteScope(scopeName);
_log.Write(GetRequestRemoteEndPoint(request), "[" + GetSession(request).Username + "] DHCP scope was deleted successfully: " + scopeName);
}
private void SetCredentials(string username, string password)
{
username = username.ToLower();
string passwordHash = GetPasswordHash(username, password);
_credentials[username] = passwordHash;
}
private void LoadCredentials(string username, string passwordHash)
{
username = username.ToLower();
_credentials[username] = passwordHash;
}
private static string GetPasswordHash(string username, string password)
{
using (HMAC hmac = new HMACSHA256(Encoding.UTF8.GetBytes(password)))
{
return BitConverter.ToString(hmac.ComputeHash(Encoding.UTF8.GetBytes(username))).Replace("-", "").ToLower();
}
}
private void StartBlockListUpdateTimer()
{
if (_blockListUpdateTimer == null)
{
_blockListUpdateTimer = new Timer(async delegate (object state)
{
try
{
if (DateTime.UtcNow > _blockListLastUpdatedOn.AddHours(BLOCK_LIST_UPDATE_AFTER_HOURS))
{
if (await _dnsServer.BlockListZoneManager.UpdateBlockListsAsync())
{
//block lists were updated
//save last updated on time
_blockListLastUpdatedOn = DateTime.UtcNow;
SaveConfigFile();
}
}
}
catch (Exception ex)
{
_log.Write("DNS Server encountered an error while updating block lists.\r\n" + ex.ToString());
}
}, null, BLOCK_LIST_UPDATE_TIMER_INITIAL_INTERVAL, BLOCK_LIST_UPDATE_TIMER_INTERVAL);
}
}
private void StopBlockListUpdateTimer()
{
if (_blockListUpdateTimer != null)
{
_blockListUpdateTimer.Dispose();
_blockListUpdateTimer = null;
}
}
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 2:
case 3:
case 4:
case 5:
case 6:
case 7:
case 8:
case 9:
case 10:
case 11:
_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 = NetProxy.CreateProxy(proxyType, address, port, credential);
if (version >= 10)
{
int count = bR.ReadByte();
_dnsServer.Proxy.BypassList.Clear();
for (int i = 0; i < count; i++)
_dnsServer.Proxy.BypassList.Add(new NetProxyBypassItem(bR.ReadShortString()));
}
}
else
{
_dnsServer.Proxy = null;
}
{
int count = bR.ReadByte();
if (count > 0)
{
NameServerAddress[] forwarders = new NameServerAddress[count];
for (int i = 0; i < count; i++)
forwarders[i] = new NameServerAddress(bR);
_dnsServer.Forwarders = forwarders;
}
}
if (version <= 10)
{
DnsTransportProtocol forwarderProtocol = (DnsTransportProtocol)bR.ReadByte();
if (_dnsServer.Forwarders != null)
{
List forwarders = new List();
foreach (NameServerAddress forwarder in _dnsServer.Forwarders)
{
if (forwarder.Protocol == forwarderProtocol)
forwarders.Add(forwarder);
else
forwarders.Add(new NameServerAddress(forwarder, forwarderProtocol));
}
_dnsServer.Forwarders = forwarders;
}
}
{
int count = bR.ReadByte();
if (count > 0)
{
if (version > 2)
{
for (int i = 0; i < count; i++)
LoadCredentials(bR.ReadShortString(), bR.ReadShortString());
}
else
{
for (int i = 0; i < count; i++)
SetCredentials(bR.ReadShortString(), bR.ReadShortString());
}
}
}
if (version <= 6)
{
int count = bR.ReadInt32();
_configDisabledZones = new List(count);
for (int i = 0; i < count; i++)
{
string domain = bR.ReadShortString();
_configDisabledZones.Add(domain);
}
}
if (version > 4)
{
//read block list urls
int count = bR.ReadByte();
for (int i = 0; i < count; i++)
_dnsServer.BlockListZoneManager.BlockListUrls.Add(new Uri(bR.ReadShortString()));
_blockListLastUpdatedOn = bR.ReadDate();
}
if (version >= 11)
{
int count = bR.ReadByte();
if (count > 0)
{
IPEndPoint[] localEndPoints = new IPEndPoint[count];
for (int i = 0; i < count; i++)
localEndPoints[i] = (IPEndPoint)EndPointExtension.Parse(bR);
_dnsServer.LocalEndPoints = localEndPoints;
}
}
else if (version >= 6)
{
int count = bR.ReadByte();
if (count > 0)
{
IPEndPoint[] localEndPoints = new IPEndPoint[count];
for (int i = 0; i < count; i++)
localEndPoints[i] = new IPEndPoint(IPAddressExtension.Parse(bR), 53);
_dnsServer.LocalEndPoints = localEndPoints;
}
}
if (version >= 8)
{
_dnsServer.EnableDnsOverHttp = bR.ReadBoolean();
_dnsServer.EnableDnsOverTls = bR.ReadBoolean();
_dnsServer.EnableDnsOverHttps = bR.ReadBoolean();
_tlsCertificatePath = bR.ReadShortString();
_tlsCertificatePassword = bR.ReadShortString();
if (_tlsCertificatePath.Length == 0)
_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.");
_webServicePort = 5380;
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.");
throw;
}
}
private void SaveConfigFile()
{
string configFile = Path.Combine(_configFolder, "dns.config");
using (MemoryStream mS = new MemoryStream())
{
//serialize config
BinaryWriter bW = new BinaryWriter(mS);
bW.Write(Encoding.ASCII.GetBytes("DS")); //format
bW.Write((byte)11); //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);
}
//bypass list
{
bW.Write(Convert.ToByte(_dnsServer.Proxy.BypassList.Count));
foreach (NetProxyBypassItem item in _dnsServer.Proxy.BypassList)
bW.WriteShortString(item.Value);
}
}
if (_dnsServer.Forwarders == null)
{
bW.Write((byte)0);
}
else
{
bW.Write(Convert.ToByte(_dnsServer.Forwarders.Count));
foreach (NameServerAddress forwarder in _dnsServer.Forwarders)
forwarder.WriteTo(bW);
}
{
bW.Write(Convert.ToByte(_credentials.Count));
foreach (KeyValuePair credential in _credentials)
{
bW.WriteShortString(credential.Key);
bW.WriteShortString(credential.Value);
}
}
//block list
{
bW.Write(Convert.ToByte(_dnsServer.BlockListZoneManager.BlockListUrls.Count));
foreach (Uri blockListUrl in _dnsServer.BlockListZoneManager.BlockListUrls)
bW.WriteShortString(blockListUrl.AbsoluteUri);
bW.Write(_blockListLastUpdatedOn);
}
{
bW.Write(Convert.ToByte(_dnsServer.LocalEndPoints.Count));
foreach (IPEndPoint localEP in _dnsServer.LocalEndPoints)
localEP.WriteTo(bW);
}
bW.Write(_dnsServer.EnableDnsOverHttp);
bW.Write(_dnsServer.EnableDnsOverTls);
bW.Write(_dnsServer.EnableDnsOverHttps);
if (_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
{
//init dns server
_dnsServer = new DnsServer(_configFolder, Path.Combine(_appFolder, "dohwww"), _log);
//init dhcp server
_dhcpServer = new DhcpServer(Path.Combine(_configFolder, "scopes"), _log);
_dhcpServer.AuthZoneManager = _dnsServer.AuthZoneManager;
//load config
LoadConfigFile();
//load all zones files
_dnsServer.AuthZoneManager.LoadAllZoneFiles();
//disable zones from old config format
if (_configDisabledZones != null)
{
foreach (string domain in _configDisabledZones)
{
AuthZoneInfo zoneInfo = _dnsServer.AuthZoneManager.GetAuthZoneInfo(domain);
if (zoneInfo != null)
{
zoneInfo.Disabled = true;
_dnsServer.AuthZoneManager.SaveZoneFile(zoneInfo.Name);
}
}
}
//load allowed zone and blocked zone
_dnsServer.AllowedZoneManager.LoadAllowedZoneFile();
_dnsServer.BlockedZoneManager.LoadBlockedZoneFile();
//load block list zone async
if (_dnsServer.BlockListZoneManager.BlockListUrls.Count > 0)
{
ThreadPool.QueueUserWorkItem(delegate (object state)
{
try
{
_dnsServer.BlockListZoneManager.LoadBlockLists();
StartBlockListUpdateTimer();
}
catch (Exception ex)
{
_log.Write(ex);
}
});
}
//start dns and dhcp
_dnsServer.Start();
_dhcpServer.Start();
//start web service
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.Name = "WebService";
_webServiceThread.IsBackground = true;
_webServiceThread.Start();
_state = ServiceState.Running;
_log.Write(new IPEndPoint(IPAddress.Any, _webServicePort), "Web Service (v" + _currentVersion.ToString() + ") was started successfully.");
}
catch (Exception ex)
{
_log.Write("Failed to start Web Service (v" + _currentVersion.ToString() + ")\r\n" + ex.ToString());
throw;
}
}
public void Stop()
{
if (_state != ServiceState.Running)
return;
_state = ServiceState.Stopping;
try
{
_webService.Stop();
_dnsServer.Stop();
_dhcpServer.Stop();
StopBlockListUpdateTimer();
StopTlsCertificateUpdateTimer();
_state = ServiceState.Stopped;
_log.Write(new IPEndPoint(IPAddress.Loopback, _webServicePort), "Web Service (v" + _currentVersion.ToString() + ") was stopped successfully.");
}
catch (Exception ex)
{
_log.Write("Failed to stop Web Service (v" + _currentVersion.ToString() + ")\r\n" + ex.ToString());
throw;
}
}
#endregion
#region properties
public string ConfigFolder
{ get { return _configFolder; } }
public int WebServicePort
{ get { return _webServicePort; } }
public string WebServiceHostname
{ get { return _webServiceHostname; } }
#endregion
}
}