From 95b3bcacab30848be366a2cf477a6c564a462ece Mon Sep 17 00:00:00 2001 From: Shreyas Zare Date: Sun, 19 May 2024 16:08:35 +0530 Subject: [PATCH] DnsWebService: implemented delayed save feature. Implemented custom https redirection middleware to preseve same hostname in redirection url. Added support for .p12 cert extension. Updated self signed cert implementation to use specific file name. Set NS Revalidation to false since many popular domains fail to resolve due to they being misconfigured. Updated config file to add support for new options. --- DnsServerCore/DnsWebService.cs | 160 +++++++++++++++++++++++++++++---- 1 file changed, 144 insertions(+), 16 deletions(-) diff --git a/DnsServerCore/DnsWebService.cs b/DnsServerCore/DnsWebService.cs index 8f61f8f5..78b1d95d 100644 --- a/DnsServerCore/DnsWebService.cs +++ b/DnsServerCore/DnsWebService.cs @@ -37,6 +37,7 @@ using System; using System.Collections.Generic; using System.IO; using System.Net; +using System.Net.Mail; using System.Net.Quic; using System.Net.Security; using System.Net.Sockets; @@ -114,6 +115,11 @@ namespace DnsServerCore List _configDisabledZones; + readonly object _saveLock = new object(); + bool _pendingSave; + readonly Timer _saveTimer; + const int SAVE_TIMER_INITIAL_INTERVAL = 10000; + #endregion #region constructor @@ -145,6 +151,25 @@ namespace DnsServerCore _dhcpApi = new WebServiceDhcpApi(this); _authApi = new WebServiceAuthApi(this); _logsApi = new WebServiceLogsApi(this); + + _saveTimer = new Timer(delegate (object state) + { + lock (_saveLock) + { + try + { + SaveConfigFileInternal(); + } + catch (Exception ex) + { + _log.Write(ex); + } + finally + { + _pendingSave = false; + } + } + }); } #endregion @@ -158,6 +183,27 @@ namespace DnsServerCore if (_disposed) return; + _saveTimer?.Dispose(); + + lock (_saveLock) + { + if (_pendingSave) + { + try + { + SaveConfigFileInternal(); + } + catch (Exception ex) + { + _log.Write(ex); + } + finally + { + _pendingSave = false; + } + } + } + await StopAsync(); if (_appsApi is not null) @@ -194,6 +240,9 @@ namespace DnsServerCore internal string ConvertToAbsolutePath(string path) { + if (path is null) + return null; + if (Path.IsPathRooted(path)) return path; @@ -250,7 +299,7 @@ namespace DnsServerCore await StartWebServiceAsync(_webServiceLocalAddresses, _webServiceHttpPort, _webServiceTlsPort, false); - SaveConfigFile(); //save reverted changes + SaveConfigFileInternal(); //save reverted changes return; } catch (Exception ex2) @@ -331,7 +380,7 @@ namespace DnsServerCore _webService = builder.Build(); if (_webServiceHttpToTlsRedirect && !safeMode && _webServiceEnableTls && (_webServiceCertificateCollection is not null)) - _webService.UseHttpsRedirection(); + _webService.Use(WebServiceHttpsRedirectionMiddleware); _webService.UseDefaultFiles(); _webService.UseStaticFiles(new StaticFileOptions() @@ -527,6 +576,15 @@ namespace DnsServerCore _webService.MapGetAndPost("/api/logs/query", _logsApi.QueryLogsAsync); } + private Task WebServiceHttpsRedirectionMiddleware(HttpContext context, RequestDelegate next) + { + if (context.Request.IsHttps) + return next(context); + + context.Response.Redirect("https://" + (context.Request.Host.HasValue ? context.Request.Host.Host : _dnsServer.ServerDomain) + (_webServiceTlsPort == 443 ? "" : ":" + _webServiceTlsPort) + context.Request.Path + (context.Request.QueryString.HasValue ? context.Request.QueryString.Value : ""), false, true); + return Task.CompletedTask; + } + private async Task WebServiceApiMiddleware(HttpContext context, RequestDelegate next) { bool needsJsonResponseObject; @@ -648,7 +706,7 @@ namespace DnsServerCore jsonWriter.WriteEndObject(); } - _log.Write(ex); + _log.Write(context.GetRemoteEndPoint(), ex); } }); } @@ -737,8 +795,15 @@ namespace DnsServerCore if (!fileInfo.Exists) throw new ArgumentException("Web Service TLS certificate file does not exists: " + tlsCertificatePath); - if (Path.GetExtension(tlsCertificatePath) != ".pfx") - throw new ArgumentException("Web Service TLS certificate file must be PKCS #12 formatted with .pfx extension: " + tlsCertificatePath); + switch (Path.GetExtension(tlsCertificatePath).ToLowerInvariant()) + { + case ".pfx": + case ".p12": + break; + + default: + throw new ArgumentException("Web Service TLS certificate file must be PKCS #12 formatted with .pfx or .p12 extension: " + tlsCertificatePath); + } X509Certificate2Collection certificateCollection = new X509Certificate2Collection(); certificateCollection.Import(tlsCertificatePath, tlsCertificatePassword, X509KeyStorageFlags.PersistKeySet); @@ -776,8 +841,15 @@ namespace DnsServerCore if (!fileInfo.Exists) throw new ArgumentException("DNS Server TLS certificate file does not exists: " + tlsCertificatePath); - if (Path.GetExtension(tlsCertificatePath) != ".pfx") - throw new ArgumentException("DNS Server TLS certificate file must be PKCS #12 formatted with .pfx extension: " + tlsCertificatePath); + switch (Path.GetExtension(tlsCertificatePath).ToLowerInvariant()) + { + case ".pfx": + case ".p12": + break; + + default: + throw new ArgumentException("DNS Server TLS certificate file must be PKCS #12 formatted with .pfx or .p12 extension: " + tlsCertificatePath); + } X509Certificate2Collection certificateCollection = new X509Certificate2Collection(); certificateCollection.Import(tlsCertificatePath, tlsCertificatePassword, X509KeyStorageFlags.PersistKeySet); @@ -790,10 +862,15 @@ namespace DnsServerCore internal void SelfSignedCertCheck(bool generateNew, bool throwException) { - string selfSignedCertificateFilePath = Path.Combine(_configFolder, "cert.pfx"); + string selfSignedCertificateFilePath = Path.Combine(_configFolder, "self-signed-cert.pfx"); if (_webServiceUseSelfSignedTlsCertificate) { + string oldSelfSignedCertificateFilePath = Path.Combine(_configFolder, "cert.pfx"); + + if (!oldSelfSignedCertificateFilePath.Equals(ConvertToAbsolutePath(_webServiceTlsCertificatePath), Environment.OSVersion.Platform == PlatformID.Win32NT ? StringComparison.OrdinalIgnoreCase : StringComparison.Ordinal) && File.Exists(oldSelfSignedCertificateFilePath) && !File.Exists(selfSignedCertificateFilePath)) + File.Move(oldSelfSignedCertificateFilePath, selfSignedCertificateFilePath); + if (generateNew || !File.Exists(selfSignedCertificateFilePath)) { RSA rsa = RSA.Create(2048); @@ -871,7 +948,7 @@ namespace DnsServerCore _log.Write("DNS Server config file was loaded: " + configFile); if (version <= 27) - SaveConfigFile(); //save as new config version to avoid loading old version next time + SaveConfigFileInternal(); //save as new config version to avoid loading old version next time } catch (FileNotFoundException) { @@ -935,7 +1012,7 @@ namespace DnsServerCore _dnsServer.RandomizeName = true; //default true to enable security feature _dnsServer.QnameMinimization = true; //default true to enable privacy feature - _dnsServer.NsRevalidation = true; //default true for security reasons + _dnsServer.NsRevalidation = false; //default false to allow resolving misconfigured zones //cache _dnsServer.CacheZoneManager.MaximumEntries = 10000; @@ -1011,7 +1088,7 @@ namespace DnsServerCore _dnsServer.StatsManager.EnableInMemoryStats = false; - SaveConfigFile(); + SaveConfigFileInternal(); } catch (Exception ex) { @@ -1044,7 +1121,7 @@ namespace DnsServerCore } } - internal void SaveConfigFile() + private void SaveConfigFileInternal() { string configFile = Path.Combine(_configFolder, "dns.config"); @@ -1065,6 +1142,18 @@ namespace DnsServerCore _log.Write("DNS Server config file was saved: " + configFile); } + internal void SaveConfigFile() + { + lock (_saveLock) + { + if (_pendingSave) + return; + + _pendingSave = true; + _saveTimer.Change(SAVE_TIMER_INITIAL_INTERVAL, Timeout.Infinite); + } + } + internal void InspectAndFixZonePermissions() { Permission permission = _authManager.GetPermission(PermissionSection.Zones); @@ -1116,7 +1205,7 @@ namespace DnsServerCore int version = bR.ReadByte(); - if ((version >= 28) && (version <= 35)) + if ((version >= 28) && (version <= 36)) { ReadConfigFrom(bR, version); } @@ -1128,6 +1217,7 @@ namespace DnsServerCore DnsClientConnection.IPv4SourceAddresses = null; DnsClientConnection.IPv6SourceAddresses = null; _webServiceEnableHttp3 = _webServiceEnableTls && IsQuicSupported(); + _dnsServer.ResponsiblePersonInternal = null; _dnsServer.AuthZoneManager.UseSoaSerialDateScheme = false; _dnsServer.ZoneTransferAllowedNetworks = null; _dnsServer.NotifyAllowedNetworks = null; @@ -1138,6 +1228,9 @@ namespace DnsServerCore _dnsServer.EDnsClientSubnetIpv6Override = null; _dnsServer.QpmLimitBypassList = null; _dnsServer.BlockingBypassList = null; + _dnsServer.CacheZoneManager.ServeStaleAnswerTtl = CacheZoneManager.SERVE_STALE_ANSWER_TTL; + _dnsServer.CacheZoneManager.ServeStaleResetTtl = CacheZoneManager.SERVE_STALE_RESET_TTL; + _dnsServer.ServeStaleMaxWaitTime = DnsServer.SERVE_STALE_MAX_WAIT_TIME; _dnsServer.ResolverLogManager = _log; _appsApi.EnableAutomaticUpdate = true; _dnsServer.StatsManager.EnableInMemoryStats = false; @@ -1244,6 +1337,19 @@ namespace DnsServerCore _zonesApi.DefaultRecordTtl = bR.ReadUInt32(); + if (version >= 36) + { + string rp = bR.ReadString(); + if (rp.Length == 0) + _dnsServer.ResponsiblePersonInternal = null; + else + _dnsServer.ResponsiblePersonInternal = new MailAddress(rp); + } + else + { + _dnsServer.ResponsiblePersonInternal = null; + } + if (version >= 33) { _dnsServer.AuthZoneManager.UseSoaSerialDateScheme = bR.ReadBoolean(); @@ -1465,6 +1571,19 @@ namespace DnsServerCore _dnsServer.ServeStale = bR.ReadBoolean(); _dnsServer.CacheZoneManager.ServeStaleTtl = bR.ReadUInt32(); + if (version >= 36) + { + _dnsServer.CacheZoneManager.ServeStaleAnswerTtl = bR.ReadUInt32(); + _dnsServer.CacheZoneManager.ServeStaleResetTtl = bR.ReadUInt32(); + _dnsServer.ServeStaleMaxWaitTime = bR.ReadInt32(); + } + else + { + _dnsServer.CacheZoneManager.ServeStaleAnswerTtl = CacheZoneManager.SERVE_STALE_ANSWER_TTL; + _dnsServer.CacheZoneManager.ServeStaleResetTtl = CacheZoneManager.SERVE_STALE_RESET_TTL; + _dnsServer.ServeStaleMaxWaitTime = DnsServer.SERVE_STALE_MAX_WAIT_TIME; + } + _dnsServer.CacheZoneManager.MaximumEntries = bR.ReadInt64(); _dnsServer.CacheZoneManager.MinimumRecordTtl = bR.ReadUInt32(); _dnsServer.CacheZoneManager.MaximumRecordTtl = bR.ReadUInt32(); @@ -2149,7 +2268,7 @@ namespace DnsServerCore if (version >= 22) _dnsServer.NsRevalidation = bR.ReadBoolean(); else - _dnsServer.NsRevalidation = true; //default true for security reasons + _dnsServer.NsRevalidation = false; //default false to allow resolving misconfigured zones if (version >= 23) { @@ -2224,7 +2343,7 @@ namespace DnsServerCore private void WriteConfigTo(BinaryWriter bW) { bW.Write(Encoding.ASCII.GetBytes("DS")); //format - bW.Write((byte)35); //version + bW.Write((byte)36); //version //web service { @@ -2270,6 +2389,12 @@ namespace DnsServerCore WriteNetworkAddresses(DnsClientConnection.IPv6SourceAddresses, bW); bW.Write(_zonesApi.DefaultRecordTtl); + + if (_dnsServer.ResponsiblePersonInternal is null) + bW.WriteShortString(""); + else + bW.WriteShortString(_dnsServer.ResponsiblePersonInternal.Address); + bW.Write(_dnsServer.AuthZoneManager.UseSoaSerialDateScheme); WriteNetworkAddresses(_dnsServer.ZoneTransferAllowedNetworks, bW); @@ -2380,6 +2505,9 @@ namespace DnsServerCore bW.Write(_saveCache); bW.Write(_dnsServer.ServeStale); bW.Write(_dnsServer.CacheZoneManager.ServeStaleTtl); + bW.Write(_dnsServer.CacheZoneManager.ServeStaleAnswerTtl); + bW.Write(_dnsServer.CacheZoneManager.ServeStaleResetTtl); + bW.Write(_dnsServer.ServeStaleMaxWaitTime); bW.Write(_dnsServer.CacheZoneManager.MaximumEntries); bW.Write(_dnsServer.CacheZoneManager.MinimumRecordTtl); @@ -2521,7 +2649,7 @@ namespace DnsServerCore try { //get initial server domain - string dnsServerDomain = Environment.MachineName.ToLower(); + string dnsServerDomain = Environment.MachineName.ToLowerInvariant(); if (!DnsClient.IsDomainNameValid(dnsServerDomain)) dnsServerDomain = "dns-server-1"; //use this name instead since machine name is not a valid domain name