Files
DnsServer/DnsServerCore/DnsWebService.cs

1224 lines
48 KiB
C#

/*
Technitium Library
Copyright (C) 2017 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 <http://www.gnu.org/licenses/>.
*/
using Newtonsoft.Json;
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.IO;
using System.Net;
using System.Text;
using System.Threading;
using TechnitiumLibrary.IO;
using TechnitiumLibrary.Net;
using TechnitiumLibrary.Net.Dns;
namespace DnsServerCore
{
public class DnsWebService
{
#region enum
enum ServiceState
{
Stopped = 0,
Running = 1,
Stopping = 2
}
#endregion
#region variables
readonly string _configFolder;
string _serverDomain;
int _webServicePort;
DnsServer _dnsServer;
HttpListener _webService;
Thread _webServiceThread;
readonly ConcurrentDictionary<string, string> _credentials = new ConcurrentDictionary<string, string>();
readonly ConcurrentDictionary<string, UserSession> _sessions = new ConcurrentDictionary<string, UserSession>();
volatile ServiceState _state = ServiceState.Stopped;
#endregion
#region constructor
public DnsWebService(string configFolder)
{
_configFolder = configFolder;
}
#endregion
#region private
private void AcceptWebRequestAsync(object state)
{
try
{
while (true)
{
HttpListenerContext context = _webService.GetContext();
ThreadPool.QueueUserWorkItem(ProcessRequestAsync, new object[] { context.Request, context.Response });
}
}
catch
{
if (_state == ServiceState.Running)
throw;
}
}
private void ProcessRequestAsync(object state)
{
object[] parameters = state as object[];
HttpListenerRequest request = parameters[0] as HttpListenerRequest;
HttpListenerResponse response = parameters[1] as HttpListenerResponse;
try
{
Uri url = request.Url;
string path = url.AbsolutePath;
if (!path.StartsWith("/"))
{
Send404(response);
return;
}
if (path.StartsWith("/api/"))
{
response.ContentType = "application/json; charset=utf-8";
response.ContentEncoding = Encoding.UTF8;
using (JsonTextWriter jsonWriter = new JsonTextWriter(new StreamWriter(response.OutputStream)))
{
jsonWriter.WriteStartObject();
try
{
switch (path)
{
case "/api/login":
Login(request, jsonWriter);
break;
case "/api/logout":
Logout(request);
break;
default:
if (!IsSessionValid(request))
throw new InvalidTokenDnsWebServiceException("Invalid token or session expired.");
jsonWriter.WritePropertyName("response");
jsonWriter.WriteStartObject();
try
{
switch (path)
{
case "/api/changePassword":
ChangePassword(request);
break;
case "/api/getDnsSettings":
GetDnsSettings(jsonWriter);
break;
case "/api/setDnsSettings":
SetDnsSettings(request);
break;
case "/api/flushDnsCache":
_dnsServer.CacheZoneRoot.Flush();
break;
case "/api/listZones":
ListZones(jsonWriter);
break;
case "/api/createZone":
CreateZone(request);
break;
case "/api/deleteZone":
DeleteZone(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;
default:
throw new DnsWebServiceException("Invalid command: " + path);
}
}
finally
{
jsonWriter.WriteEndObject();
}
break;
}
jsonWriter.WritePropertyName("status");
jsonWriter.WriteValue("ok");
}
catch (InvalidTokenDnsWebServiceException ex)
{
jsonWriter.WritePropertyName("status");
jsonWriter.WriteValue("invalid-token");
jsonWriter.WritePropertyName("errorMessage");
jsonWriter.WriteValue(ex.Message);
}
catch (Exception ex)
{
jsonWriter.WritePropertyName("status");
jsonWriter.WriteValue("error");
jsonWriter.WritePropertyName("errorMessage");
jsonWriter.WriteValue(ex.Message);
jsonWriter.WritePropertyName("stackTrace");
jsonWriter.WriteValue(ex.StackTrace);
}
jsonWriter.WriteEndObject();
}
}
else
{
if (path.Contains("/../"))
{
Send404(response);
return;
}
if (path == "/")
path = "/index.html";
path = "www" + path;
if (!File.Exists(path))
{
Send404(response);
return;
}
SendFile(response, path);
}
}
catch (Exception ex)
{
Send500(response, ex);
}
}
private void Send500(HttpListenerResponse response, Exception ex)
{
Send500(response, ex.ToString());
}
private void Send500(HttpListenerResponse response, string message)
{
byte[] buffer = Encoding.UTF8.GetBytes("<h1>500 Internal Server Error</h1><p>" + message + "</p>");
response.StatusCode = 500;
response.ContentType = "text/html";
response.ContentLength64 = buffer.Length;
using (Stream stream = response.OutputStream)
{
stream.Write(buffer, 0, buffer.Length);
}
}
private void Send404(HttpListenerResponse response)
{
byte[] buffer = Encoding.UTF8.GetBytes("<h1>404 Not Found</h1>");
response.StatusCode = 404;
response.ContentType = "text/html";
response.ContentLength64 = buffer.Length;
using (Stream stream = response.OutputStream)
{
stream.Write(buffer, 0, buffer.Length);
}
}
private void SendFile(HttpListenerResponse response, string path)
{
using (FileStream fS = new FileStream(path, FileMode.Open, FileAccess.Read))
{
response.ContentType = WebUtilities.GetContentType(path).MediaType;
response.ContentLength64 = fS.Length;
response.AddHeader("Cache-Control", "private, max-age=300");
using (Stream stream = response.OutputStream)
{
OffsetStream.StreamCopy(fS, stream);
}
}
}
private string CreateSession(string username)
{
string token = BinaryNumber.GenerateRandomNumber256().ToString();
if (!_sessions.TryAdd(token, new UserSession(username)))
throw new DnsWebServiceException("Error while creating session. Please try again.");
return token;
}
private UserSession GetSession(string token)
{
if (_sessions.TryGetValue(token, out UserSession session))
return session;
return null;
}
private UserSession DeleteSession(string token)
{
if (_sessions.TryRemove(token, out UserSession session))
return session;
return null;
}
private void Login(HttpListenerRequest request, JsonTextWriter jsonWriter)
{
string strUsername = request.QueryString["user"];
if (string.IsNullOrEmpty(strUsername))
throw new DnsWebServiceException("Parameter 'user' missing.");
string strPassword = request.QueryString["pass"];
if (string.IsNullOrEmpty(strPassword))
throw new DnsWebServiceException("Parameter 'pass' missing.");
strUsername = strUsername.ToLower();
if (!_credentials.TryGetValue(strUsername, out string password) || (password != strPassword))
throw new DnsWebServiceException("Invalid username or password.");
string token = CreateSession(strUsername);
jsonWriter.WritePropertyName("token");
jsonWriter.WriteValue(token);
}
private bool IsSessionValid(HttpListenerRequest request)
{
string strToken = request.QueryString["token"];
if (string.IsNullOrEmpty(strToken))
throw new DnsWebServiceException("Parameter 'token' missing.");
UserSession session = GetSession(strToken);
if (session == null)
return false;
if (session.HasExpired())
{
_sessions.TryRemove(strToken, out UserSession x);
return false;
}
session.UpdateLastSeen();
return true;
}
private void ChangePassword(HttpListenerRequest request)
{
string strToken = request.QueryString["token"];
if (string.IsNullOrEmpty(strToken))
throw new DnsWebServiceException("Parameter 'token' missing.");
string strPassword = request.QueryString["pass"];
if (string.IsNullOrEmpty(strPassword))
throw new DnsWebServiceException("Parameter 'pass' missing.");
UserSession session = GetSession(strToken);
if (session == null)
throw new DnsWebServiceException("User session does not exists.");
SetCredentials(session.Username, strPassword);
SaveConfigFile();
}
private void Logout(HttpListenerRequest request)
{
string strToken = request.QueryString["token"];
if (string.IsNullOrEmpty(strToken))
throw new DnsWebServiceException("Parameter 'token' missing.");
DeleteSession(strToken);
}
private void GetDnsSettings(JsonTextWriter jsonWriter)
{
jsonWriter.WritePropertyName("serverDomain");
jsonWriter.WriteValue(_serverDomain);
jsonWriter.WritePropertyName("webServicePort");
jsonWriter.WriteValue(_webServicePort);
jsonWriter.WritePropertyName("preferIPv6");
jsonWriter.WriteValue(_dnsServer.PreferIPv6);
jsonWriter.WritePropertyName("allowRecursion");
jsonWriter.WriteValue(_dnsServer.AllowRecursion);
jsonWriter.WritePropertyName("forwarders");
if (_dnsServer.Forwarders == null)
{
jsonWriter.WriteNull();
}
else
{
jsonWriter.WriteStartArray();
foreach (NameServerAddress forwarder in _dnsServer.Forwarders)
jsonWriter.WriteValue(forwarder.EndPoint.Address.ToString());
jsonWriter.WriteEndArray();
}
}
private void SetDnsSettings(HttpListenerRequest request)
{
string strServerDomain = request.QueryString["serverDomain"];
if (!string.IsNullOrEmpty(strServerDomain))
_serverDomain = strServerDomain;
string strWebServicePort = request.QueryString["webServicePort"];
if (!string.IsNullOrEmpty(strWebServicePort))
_webServicePort = int.Parse(strWebServicePort);
string strPreferIPv6 = request.QueryString["preferIPv6"];
if (!string.IsNullOrEmpty(strPreferIPv6))
_dnsServer.PreferIPv6 = bool.Parse(strPreferIPv6);
string strAllowRecursion = request.QueryString["allowRecursion"];
if (!string.IsNullOrEmpty(strAllowRecursion))
_dnsServer.AllowRecursion = bool.Parse(strAllowRecursion);
string strForwarders = request.QueryString["forwarders"];
if (!string.IsNullOrEmpty(strForwarders))
{
if (strForwarders == "false")
{
_dnsServer.Forwarders = null;
}
else
{
string[] strForwardersList = strForwarders.Split(',');
NameServerAddress[] forwarders = new NameServerAddress[strForwardersList.Length];
for (int i = 0; i < strForwardersList.Length; i++)
forwarders[i] = new NameServerAddress(IPAddress.Parse(strForwardersList[i]));
_dnsServer.Forwarders = forwarders;
}
}
SaveConfigFile();
}
private void ListZones(JsonTextWriter jsonWriter)
{
string[] zones = _dnsServer.AuthoritativeZoneRoot.ListAuthoritativeZones();
jsonWriter.WritePropertyName("zones");
jsonWriter.WriteStartArray();
foreach (string zone in zones)
jsonWriter.WriteValue(zone);
jsonWriter.WriteEndArray();
}
private void CreateZone(HttpListenerRequest request)
{
string domain = request.QueryString["domain"];
if (string.IsNullOrEmpty(domain))
throw new DnsWebServiceException("Parameter 'domain' missing.");
_dnsServer.AuthoritativeZoneRoot.SetRecords(domain, DnsResourceRecordType.SOA, 14400, new DnsResourceRecordData[] { new DnsSOARecord(_serverDomain, "hostmaster." + _serverDomain, uint.Parse(DateTime.UtcNow.ToString("yyyymmddHH")), 28800, 7200, 604800, 600) });
SaveZoneFile(domain);
}
private void DeleteZone(HttpListenerRequest request)
{
string domain = request.QueryString["domain"];
if (string.IsNullOrEmpty(domain))
throw new DnsWebServiceException("Parameter 'domain' missing.");
_dnsServer.AuthoritativeZoneRoot.DeleteZone(domain);
DeleteZoneFile(domain);
}
private void AddRecord(HttpListenerRequest request)
{
string domain = request.QueryString["domain"];
if (string.IsNullOrEmpty(domain))
throw new DnsWebServiceException("Parameter 'domain' missing.");
string strType = request.QueryString["type"];
if (string.IsNullOrEmpty(strType))
throw new DnsWebServiceException("Parameter 'type' missing.");
DnsResourceRecordType type = (DnsResourceRecordType)Enum.Parse(typeof(DnsResourceRecordType), strType);
string value = request.QueryString["value"];
if (string.IsNullOrEmpty(value))
throw new DnsWebServiceException("Parameter 'value' missing.");
uint ttl;
string strTtl = request.QueryString["ttl"];
if (string.IsNullOrEmpty(strTtl))
ttl = 3600;
else
ttl = uint.Parse(strTtl);
switch (type)
{
case DnsResourceRecordType.A:
_dnsServer.AuthoritativeZoneRoot.AddRecord(domain, type, ttl, new DnsARecord(IPAddress.Parse(value)));
break;
case DnsResourceRecordType.AAAA:
_dnsServer.AuthoritativeZoneRoot.AddRecord(domain, type, ttl, new DnsAAAARecord(IPAddress.Parse(value)));
break;
case DnsResourceRecordType.MX:
string preference = request.QueryString["preference"];
if (string.IsNullOrEmpty(preference))
throw new DnsWebServiceException("Parameter 'preference' missing.");
_dnsServer.AuthoritativeZoneRoot.AddRecord(domain, type, ttl, new DnsMXRecord(ushort.Parse(preference), value));
break;
case DnsResourceRecordType.TXT:
_dnsServer.AuthoritativeZoneRoot.AddRecord(domain, type, ttl, new DnsTXTRecord(value));
break;
case DnsResourceRecordType.NS:
_dnsServer.AuthoritativeZoneRoot.AddRecord(domain, type, ttl, new DnsNSRecord(value));
break;
case DnsResourceRecordType.PTR:
_dnsServer.AuthoritativeZoneRoot.SetRecords(domain, type, ttl, new DnsResourceRecordData[] { new DnsPTRRecord(value) });
break;
case DnsResourceRecordType.CNAME:
_dnsServer.AuthoritativeZoneRoot.SetRecords(domain, type, ttl, new DnsResourceRecordData[] { new DnsCNAMERecord(value) });
break;
default:
throw new DnsWebServiceException("Type not supported for AddRecords().");
}
SaveZoneFile(domain);
}
private void GetRecords(HttpListenerRequest request, JsonTextWriter jsonWriter)
{
string domain = request.QueryString["domain"];
if (string.IsNullOrEmpty(domain))
throw new DnsWebServiceException("Parameter 'domain' missing.");
DnsResourceRecord[] records = _dnsServer.AuthoritativeZoneRoot.GetRecords(domain);
if (records == null)
{
jsonWriter.WritePropertyName("records");
jsonWriter.WriteStartArray();
jsonWriter.WriteEndArray();
return;
}
Dictionary<string, Dictionary<DnsResourceRecordType, List<DnsResourceRecord>>> groupedByDomainRecords = Zone.GroupRecords(records);
jsonWriter.WritePropertyName("records");
jsonWriter.WriteStartArray();
foreach (KeyValuePair<string, Dictionary<DnsResourceRecordType, List<DnsResourceRecord>>> groupedByTypeRecords in groupedByDomainRecords)
{
foreach (KeyValuePair<DnsResourceRecordType, List<DnsResourceRecord>> groupedRecords in groupedByTypeRecords.Value)
{
foreach (DnsResourceRecord resourceRecord in groupedRecords.Value)
{
jsonWriter.WriteStartObject();
jsonWriter.WritePropertyName("name");
jsonWriter.WriteValue(resourceRecord.Name);
jsonWriter.WritePropertyName("type");
jsonWriter.WriteValue(resourceRecord.Type.ToString());
jsonWriter.WritePropertyName("ttl");
jsonWriter.WriteValue(resourceRecord.TTLValue);
jsonWriter.WritePropertyName("rData");
jsonWriter.WriteStartObject();
switch (resourceRecord.Type)
{
case DnsResourceRecordType.A:
{
jsonWriter.WritePropertyName("value");
jsonWriter.WriteValue((resourceRecord.RDATA as DnsARecord).IPAddress);
}
break;
case DnsResourceRecordType.AAAA:
{
jsonWriter.WritePropertyName("value");
jsonWriter.WriteValue((resourceRecord.RDATA as DnsAAAARecord).IPAddress);
}
break;
case DnsResourceRecordType.SOA:
{
DnsSOARecord rdata = resourceRecord.RDATA as DnsSOARecord;
jsonWriter.WritePropertyName("masterNameServer");
jsonWriter.WriteValue(rdata.MasterNameServer);
jsonWriter.WritePropertyName("responsiblePerson");
jsonWriter.WriteValue(rdata.ResponsiblePerson);
jsonWriter.WritePropertyName("serial");
jsonWriter.WriteValue(rdata.Serial);
jsonWriter.WritePropertyName("refresh");
jsonWriter.WriteValue(rdata.Refresh);
jsonWriter.WritePropertyName("retry");
jsonWriter.WriteValue(rdata.Retry);
jsonWriter.WritePropertyName("expire");
jsonWriter.WriteValue(rdata.Expire);
jsonWriter.WritePropertyName("minimum");
jsonWriter.WriteValue(rdata.Minimum);
}
break;
case DnsResourceRecordType.PTR:
{
DnsPTRRecord rdata = resourceRecord.RDATA as DnsPTRRecord;
jsonWriter.WritePropertyName("value");
jsonWriter.WriteValue(rdata.PTRDomainName);
}
break;
case DnsResourceRecordType.MX:
{
DnsMXRecord rdata = resourceRecord.RDATA as DnsMXRecord;
jsonWriter.WritePropertyName("preference");
jsonWriter.WriteValue(rdata.Preference);
jsonWriter.WritePropertyName("value");
jsonWriter.WriteValue(rdata.Exchange);
}
break;
case DnsResourceRecordType.TXT:
{
jsonWriter.WritePropertyName("value");
jsonWriter.WriteValue((resourceRecord.RDATA as DnsTXTRecord).TXTData);
}
break;
case DnsResourceRecordType.NS:
{
jsonWriter.WritePropertyName("value");
jsonWriter.WriteValue((resourceRecord.RDATA as DnsNSRecord).NSDomainName);
}
break;
case DnsResourceRecordType.CNAME:
{
DnsCNAMERecord rdata = resourceRecord.RDATA as DnsCNAMERecord;
jsonWriter.WritePropertyName("value");
jsonWriter.WriteValue(rdata.CNAMEDomainName);
}
break;
default:
{
jsonWriter.WritePropertyName("value");
using (MemoryStream mS = new MemoryStream())
{
resourceRecord.RDATA.WriteTo(mS, new List<DnsDomainOffset>());
jsonWriter.WriteValue(Convert.ToBase64String(mS.ToArray()));
}
}
break;
}
jsonWriter.WriteEndObject();
jsonWriter.WriteEndObject();
}
}
}
jsonWriter.WriteEndArray();
}
private void DeleteRecord(HttpListenerRequest request)
{
string domain = request.QueryString["domain"];
if (string.IsNullOrEmpty(domain))
throw new DnsWebServiceException("Parameter 'domain' missing.");
string strType = request.QueryString["type"];
if (string.IsNullOrEmpty(strType))
throw new DnsWebServiceException("Parameter 'type' missing.");
DnsResourceRecordType type = (DnsResourceRecordType)Enum.Parse(typeof(DnsResourceRecordType), strType);
string value = request.QueryString["value"];
if (string.IsNullOrEmpty(value))
throw new DnsWebServiceException("Parameter 'value' missing.");
switch (type)
{
case DnsResourceRecordType.A:
_dnsServer.AuthoritativeZoneRoot.DeleteRecord(domain, type, new DnsARecord(IPAddress.Parse(value)));
break;
case DnsResourceRecordType.AAAA:
_dnsServer.AuthoritativeZoneRoot.DeleteRecord(domain, type, new DnsAAAARecord(IPAddress.Parse(value)));
break;
case DnsResourceRecordType.MX:
_dnsServer.AuthoritativeZoneRoot.DeleteRecord(domain, type, new DnsMXRecord(0, value));
break;
case DnsResourceRecordType.TXT:
_dnsServer.AuthoritativeZoneRoot.DeleteRecord(domain, type, new DnsTXTRecord(value));
break;
case DnsResourceRecordType.NS:
_dnsServer.AuthoritativeZoneRoot.DeleteRecord(domain, type, new DnsNSRecord(value));
break;
case DnsResourceRecordType.CNAME:
case DnsResourceRecordType.PTR:
_dnsServer.AuthoritativeZoneRoot.DeleteRecords(domain, type);
break;
default:
throw new DnsWebServiceException("Type not supported for DeleteRecord().");
}
SaveZoneFile(domain);
}
private void UpdateRecord(HttpListenerRequest request)
{
string strType = request.QueryString["type"];
if (string.IsNullOrEmpty(strType))
throw new DnsWebServiceException("Parameter 'type' missing.");
DnsResourceRecordType type = (DnsResourceRecordType)Enum.Parse(typeof(DnsResourceRecordType), strType);
string domain = request.QueryString["domain"];
if (string.IsNullOrEmpty(domain))
throw new DnsWebServiceException("Parameter 'domain' missing.");
string oldDomain = request.QueryString["oldDomain"];
if (string.IsNullOrEmpty(oldDomain))
oldDomain = domain;
string value = request.QueryString["value"];
string oldValue = request.QueryString["oldValue"];
uint ttl;
string strTtl = request.QueryString["ttl"];
if (string.IsNullOrEmpty(strTtl))
ttl = 3600;
else
ttl = uint.Parse(strTtl);
switch (type)
{
case DnsResourceRecordType.A:
_dnsServer.AuthoritativeZoneRoot.UpdateRecord(new DnsResourceRecord(oldDomain, type, DnsClass.IN, 0, new DnsARecord(IPAddress.Parse(oldValue))), new DnsResourceRecord(domain, type, DnsClass.IN, ttl, new DnsARecord(IPAddress.Parse(value))));
break;
case DnsResourceRecordType.AAAA:
_dnsServer.AuthoritativeZoneRoot.UpdateRecord(new DnsResourceRecord(oldDomain, type, DnsClass.IN, 0, new DnsAAAARecord(IPAddress.Parse(oldValue))), new DnsResourceRecord(domain, type, DnsClass.IN, ttl, new DnsAAAARecord(IPAddress.Parse(value))));
break;
case DnsResourceRecordType.MX:
string preference = request.QueryString["preference"];
if (string.IsNullOrEmpty(preference))
throw new DnsWebServiceException("Parameter 'preference' missing.");
_dnsServer.AuthoritativeZoneRoot.UpdateRecord(new DnsResourceRecord(oldDomain, type, DnsClass.IN, 0, new DnsMXRecord(0, oldValue)), new DnsResourceRecord(domain, type, DnsClass.IN, ttl, new DnsMXRecord(ushort.Parse(preference), value)));
break;
case DnsResourceRecordType.TXT:
_dnsServer.AuthoritativeZoneRoot.UpdateRecord(new DnsResourceRecord(oldDomain, type, DnsClass.IN, 0, new DnsTXTRecord(oldValue)), new DnsResourceRecord(domain, type, DnsClass.IN, ttl, new DnsTXTRecord(value)));
break;
case DnsResourceRecordType.NS:
_dnsServer.AuthoritativeZoneRoot.UpdateRecord(new DnsResourceRecord(oldDomain, type, DnsClass.IN, 0, new DnsNSRecord(oldValue)), new DnsResourceRecord(domain, type, DnsClass.IN, ttl, new DnsNSRecord(value)));
break;
case DnsResourceRecordType.SOA:
{
string masterNameServer = request.QueryString["masterNameServer"];
if (string.IsNullOrEmpty(masterNameServer))
throw new DnsWebServiceException("Parameter 'masterNameServer' missing.");
string responsiblePerson = request.QueryString["responsiblePerson"];
if (string.IsNullOrEmpty(responsiblePerson))
throw new DnsWebServiceException("Parameter 'responsiblePerson' missing.");
string serial = request.QueryString["serial"];
if (string.IsNullOrEmpty(serial))
throw new DnsWebServiceException("Parameter 'serial' missing.");
string refresh = request.QueryString["refresh"];
if (string.IsNullOrEmpty(refresh))
throw new DnsWebServiceException("Parameter 'refresh' missing.");
string retry = request.QueryString["retry"];
if (string.IsNullOrEmpty(retry))
throw new DnsWebServiceException("Parameter 'retry' missing.");
string expire = request.QueryString["expire"];
if (string.IsNullOrEmpty(expire))
throw new DnsWebServiceException("Parameter 'expire' missing.");
string minimum = request.QueryString["minimum"];
if (string.IsNullOrEmpty(minimum))
throw new DnsWebServiceException("Parameter 'minimum' missing.");
_dnsServer.AuthoritativeZoneRoot.SetRecords(domain, type, ttl, new DnsResourceRecordData[] { new DnsSOARecord(masterNameServer, responsiblePerson, uint.Parse(serial), uint.Parse(refresh), uint.Parse(retry), uint.Parse(expire), uint.Parse(minimum)) });
}
break;
case DnsResourceRecordType.PTR:
_dnsServer.AuthoritativeZoneRoot.UpdateRecord(new DnsResourceRecord(oldDomain, type, DnsClass.IN, 0, new DnsPTRRecord(oldValue)), new DnsResourceRecord(domain, type, DnsClass.IN, ttl, new DnsPTRRecord(value)));
break;
case DnsResourceRecordType.CNAME:
_dnsServer.AuthoritativeZoneRoot.UpdateRecord(new DnsResourceRecord(oldDomain, type, DnsClass.IN, 0, new DnsCNAMERecord(oldValue)), new DnsResourceRecord(domain, type, DnsClass.IN, ttl, new DnsCNAMERecord(value)));
break;
default:
throw new DnsWebServiceException("Type not supported for UpdateRecords().");
}
SaveZoneFile(domain);
}
private void SetCredentials(string username, string password)
{
username = username.ToLower();
_credentials.AddOrUpdate(username, password, delegate (string key, string oldValue)
{
return password;
});
}
private void LoadZoneFiles()
{
string[] zoneFiles = Directory.GetFiles(_configFolder, "*.zone");
if (zoneFiles.Length == 0)
{
{
_dnsServer.AuthoritativeZoneRoot.SetRecords("localhost", DnsResourceRecordType.SOA, 14400, new DnsResourceRecordData[] { new DnsSOARecord("localhost", "hostmaster.localhost", uint.Parse(DateTime.UtcNow.ToString("yyyymmddHH")), 28800, 7200, 604800, 600) });
_dnsServer.AuthoritativeZoneRoot.SetRecords("localhost", DnsResourceRecordType.A, 3600, new DnsResourceRecordData[] { new DnsARecord(IPAddress.Loopback) });
_dnsServer.AuthoritativeZoneRoot.SetRecords("localhost", DnsResourceRecordType.AAAA, 3600, new DnsResourceRecordData[] { new DnsAAAARecord(IPAddress.IPv6Loopback) });
SaveZoneFile("localhost");
}
{
string prtDomain = new DnsQuestionRecord(IPAddress.Loopback, DnsClass.IN).Name;
_dnsServer.AuthoritativeZoneRoot.SetRecords(prtDomain, DnsResourceRecordType.SOA, 14400, new DnsResourceRecordData[] { new DnsSOARecord("localhost", "hostmaster.localhost", uint.Parse(DateTime.UtcNow.ToString("yyyymmddHH")), 28800, 7200, 604800, 600) });
_dnsServer.AuthoritativeZoneRoot.SetRecords(prtDomain, DnsResourceRecordType.PTR, 3600, new DnsResourceRecordData[] { new DnsPTRRecord("localhost") });
SaveZoneFile(prtDomain);
}
{
string prtDomain = new DnsQuestionRecord(IPAddress.IPv6Loopback, DnsClass.IN).Name;
_dnsServer.AuthoritativeZoneRoot.SetRecords(prtDomain, DnsResourceRecordType.SOA, 14400, new DnsResourceRecordData[] { new DnsSOARecord("localhost", "hostmaster.localhost", uint.Parse(DateTime.UtcNow.ToString("yyyymmddHH")), 28800, 7200, 604800, 600) });
_dnsServer.AuthoritativeZoneRoot.SetRecords(prtDomain, DnsResourceRecordType.PTR, 3600, new DnsResourceRecordData[] { new DnsPTRRecord("localhost") });
SaveZoneFile(prtDomain);
}
}
else
{
foreach (string zoneFile in zoneFiles)
LoadZoneFile(zoneFile);
}
}
private void LoadZoneFile(string zoneFile)
{
using (FileStream fS = new FileStream(zoneFile, FileMode.Open, FileAccess.Read))
{
BincodingDecoder decoder = new BincodingDecoder(fS, "DZ");
switch (decoder.Version)
{
case 1:
List<Bincoding> entries = decoder.DecodeNext().GetList();
DnsResourceRecord[] records = new DnsResourceRecord[entries.Count];
for (int i = 0; i < entries.Count; i++)
records[i] = new DnsResourceRecord(entries[i].GetValueStream());
_dnsServer.AuthoritativeZoneRoot.SetRecords(records);
break;
default:
throw new IOException("Dns Zone file version not supported: " + decoder.Version);
}
}
}
private void SaveZoneFile(string domain)
{
domain = domain.ToLower();
DnsResourceRecord[] records = _dnsServer.AuthoritativeZoneRoot.GetRecords(domain, false);
if ((records == null) || (records.Length == 0))
{
DeleteZoneFile(domain, false);
}
else
{
using (FileStream fS = new FileStream(Path.Combine(_configFolder, domain + ".zone"), FileMode.Create, FileAccess.Write))
{
BincodingEncoder encoder = new BincodingEncoder(fS, "DZ", 1);
encoder.Encode(records);
}
}
}
private void DeleteZoneFile(string domain, bool deleteSubDomains = true)
{
domain = domain.ToLower();
if (deleteSubDomains)
{
string[] zoneFiles = Directory.GetFiles(_configFolder, "*." + domain + ".zone");
foreach (string zoneFile in zoneFiles)
File.Delete(zoneFile);
}
File.Delete(Path.Combine(_configFolder, domain + ".zone"));
}
private void LoadConfigFile()
{
try
{
using (FileStream fS = new FileStream(Path.Combine(_configFolder, "dns.config"), FileMode.Open, FileAccess.Read))
{
BincodingDecoder decoder = new BincodingDecoder(fS, "DS");
switch (decoder.Version)
{
case 1:
while (true)
{
Bincoding item = decoder.DecodeNext();
if (item.Type == BincodingType.NULL)
break;
if (item.Type == BincodingType.KEY_VALUE_PAIR)
{
KeyValuePair<string, Bincoding> entry = item.GetKeyValuePair();
switch (entry.Key)
{
case "serverDomain":
_serverDomain = entry.Value.GetStringValue();
break;
case "webServicePort":
_webServicePort = entry.Value.GetIntegerValue();
break;
case "dnsPreferIPv6":
_dnsServer.PreferIPv6 = entry.Value.GetBooleanValue();
break;
case "dnsAllowRecursion":
_dnsServer.AllowRecursion = entry.Value.GetBooleanValue();
break;
case "dnsForwarders":
List<Bincoding> entries = entry.Value.GetList();
NameServerAddress[] forwarders = new NameServerAddress[entries.Count];
for (int i = 0; i < entries.Count; i++)
forwarders[i] = new NameServerAddress(IPAddress.Parse(entries[i].GetStringValue()));
_dnsServer.Forwarders = forwarders;
break;
case "credentials":
foreach (KeyValuePair<string, Bincoding> credential in entry.Value.GetDictionary())
SetCredentials(credential.Key, credential.Value.GetStringValue());
break;
}
}
}
break;
default:
throw new IOException("Dns Config file version not supported: " + decoder.Version);
}
}
}
catch (IOException)
{
_serverDomain = System.Environment.MachineName;
_webServicePort = 5380;
SetCredentials("admin", "admin");
_dnsServer.AllowRecursion = true;
SaveConfigFile();
}
}
private void SaveConfigFile()
{
using (FileStream fS = new FileStream(Path.Combine(_configFolder, "dns.config"), FileMode.Create, FileAccess.Write))
{
BincodingEncoder encoder = new BincodingEncoder(fS, "DS", 1);
encoder.Encode("serverDomain", _serverDomain);
encoder.Encode("webServicePort", _webServicePort);
encoder.Encode("dnsPreferIPv6", _dnsServer.PreferIPv6);
encoder.Encode("dnsAllowRecursion", _dnsServer.AllowRecursion);
if (_dnsServer.Forwarders != null)
{
List<Bincoding> forwarders = new List<Bincoding>();
foreach (NameServerAddress forwarder in _dnsServer.Forwarders)
forwarders.Add(Bincoding.GetValue(forwarder.EndPoint.Address.ToString()));
encoder.Encode("dnsForwarders", forwarders);
}
Dictionary<string, Bincoding> credentials = new Dictionary<string, Bincoding>();
foreach (KeyValuePair<string, string> credential in _credentials)
credentials.Add(credential.Key, Bincoding.GetValue(credential.Value));
encoder.Encode("credentials", credentials);
encoder.EncodeNull();
}
}
#endregion
#region public
public void Start()
{
if (_state != ServiceState.Stopped)
return;
_dnsServer = new DnsServer();
LoadConfigFile();
LoadZoneFiles();
_dnsServer.Start();
try
{
_webService = new HttpListener();
_webService.Prefixes.Add("http://localhost:" + _webServicePort + "/");
_webService.Prefixes.Add("http://127.0.0.1:" + _webServicePort + "/");
_webService.Prefixes.Add("http://*:" + _webServicePort + "/");
_webService.Start();
}
catch
{
_webService = new HttpListener();
_webService.Prefixes.Add("http://localhost:" + _webServicePort + "/");
_webService.Prefixes.Add("http://127.0.0.1:" + _webServicePort + "/");
_webService.Start();
}
_webServiceThread = new Thread(AcceptWebRequestAsync);
_webServiceThread.IsBackground = true;
_webServiceThread.Start();
}
public void Stop()
{
if (_state != ServiceState.Running)
return;
_state = ServiceState.Stopping;
_webService.Stop();
_dnsServer.Stop();
_state = ServiceState.Stopped;
}
#endregion
}
public class UserSession
{
#region variables
const int SESSION_TIMEOUT = 30 * 60 * 1000; //30 mins
readonly string _username;
DateTime _lastSeen;
#endregion
#region constructor
public UserSession(string username)
{
_username = username;
_lastSeen = DateTime.UtcNow;
}
#endregion
#region public
public void UpdateLastSeen()
{
_lastSeen = DateTime.UtcNow;
}
public bool HasExpired()
{
return _lastSeen.AddMilliseconds(SESSION_TIMEOUT) < DateTime.UtcNow;
}
#endregion
#region properties
public string Username
{ get { return _username; } }
#endregion
}
public class DnsWebServiceException : Exception
{
#region constructors
public DnsWebServiceException()
: base()
{ }
public DnsWebServiceException(string message)
: base(message)
{ }
public DnsWebServiceException(string message, Exception innerException)
: base(message, innerException)
{ }
protected DnsWebServiceException(System.Runtime.Serialization.SerializationInfo info, System.Runtime.Serialization.StreamingContext context)
: base(info, context)
{ }
#endregion
}
public class InvalidTokenDnsWebServiceException : Exception
{
#region constructors
public InvalidTokenDnsWebServiceException()
: base()
{ }
public InvalidTokenDnsWebServiceException(string message)
: base(message)
{ }
public InvalidTokenDnsWebServiceException(string message, Exception innerException)
: base(message, innerException)
{ }
protected InvalidTokenDnsWebServiceException(System.Runtime.Serialization.SerializationInfo info, System.Runtime.Serialization.StreamingContext context)
: base(info, context)
{ }
#endregion
}
}