/* Technitium DNS Server Copyright (C) 2023 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.ApplicationCommon; using System; using System.Collections.Concurrent; using System.Collections.Generic; using System.IO; using System.IO.Compression; using System.Text; using System.Threading.Tasks; namespace DnsServerCore.Dns.Applications { public sealed class DnsApplicationManager : IDisposable { #region variables readonly DnsServer _dnsServer; readonly string _appsPath; readonly ConcurrentDictionary _applications = new ConcurrentDictionary(); IReadOnlyList _dnsRequestControllers = Array.Empty(); IReadOnlyList _dnsAuthoritativeRequestHandlers = Array.Empty(); IReadOnlyList _dnsRequestBlockingHandlers = Array.Empty(); IReadOnlyList _dnsQueryLoggers = Array.Empty(); IReadOnlyList _dnsPostProcessors = Array.Empty(); #endregion #region constructor public DnsApplicationManager(DnsServer dnsServer) { _dnsServer = dnsServer; _appsPath = Path.Combine(_dnsServer.ConfigFolder, "apps"); if (!Directory.Exists(_appsPath)) Directory.CreateDirectory(_appsPath); } #endregion #region IDisposable bool _disposed; private void Dispose(bool disposing) { if (_disposed) return; if (disposing) { if (_applications != null) UnloadAllApplications(); } _disposed = true; } public void Dispose() { Dispose(true); } #endregion #region private private async Task LoadApplicationAsync(string applicationFolder, bool refreshAppObjectList) { string applicationName = Path.GetFileName(applicationFolder); DnsApplication application = new DnsApplication(new DnsServerInternal(_dnsServer, applicationName, applicationFolder), applicationName); await application.InitializeAsync(); if (!_applications.TryAdd(application.Name, application)) { application.Dispose(); throw new DnsServerException("DNS application already exists: " + application.Name); } if (refreshAppObjectList) RefreshAppObjectLists(); return application; } private void UnloadApplication(string applicationName) { if (!_applications.TryRemove(applicationName, out DnsApplication existingApp)) throw new DnsServerException("DNS application does not exists: " + applicationName); RefreshAppObjectLists(); existingApp.Dispose(); } private void RefreshAppObjectLists() { List dnsRequestControllers = new List(1); List dnsAuthoritativeRequestHandlers = new List(1); List dnsRequestBlockingHandlers = new List(1); List dnsQueryLoggers = new List(1); List dnsPostProcessors = new List(1); foreach (KeyValuePair application in _applications) { foreach (KeyValuePair controller in application.Value.DnsRequestControllers) dnsRequestControllers.Add(controller.Value); foreach (KeyValuePair handler in application.Value.DnsAuthoritativeRequestHandlers) dnsAuthoritativeRequestHandlers.Add(handler.Value); foreach (KeyValuePair blocker in application.Value.DnsRequestBlockingHandler) dnsRequestBlockingHandlers.Add(blocker.Value); foreach (KeyValuePair logger in application.Value.DnsQueryLoggers) dnsQueryLoggers.Add(logger.Value); foreach (KeyValuePair processor in application.Value.DnsPostProcessors) dnsPostProcessors.Add(processor.Value); } _dnsRequestControllers = dnsRequestControllers; _dnsAuthoritativeRequestHandlers = dnsAuthoritativeRequestHandlers; _dnsRequestBlockingHandlers = dnsRequestBlockingHandlers; _dnsQueryLoggers = dnsQueryLoggers; _dnsPostProcessors = dnsPostProcessors; } #endregion #region public public void UnloadAllApplications() { foreach (KeyValuePair application in _applications) { try { application.Value.Dispose(); } catch (Exception ex) { LogManager log = _dnsServer.LogManager; if (log != null) log.Write(ex); } } _applications.Clear(); _dnsRequestControllers = Array.Empty(); _dnsAuthoritativeRequestHandlers = Array.Empty(); _dnsRequestBlockingHandlers = Array.Empty(); _dnsQueryLoggers = Array.Empty(); _dnsPostProcessors = Array.Empty(); } public void LoadAllApplications() { UnloadAllApplications(); foreach (string applicationFolder in Directory.GetDirectories(_appsPath)) { Task.Run(async delegate () { try { _ = await LoadApplicationAsync(applicationFolder, false); RefreshAppObjectLists(); LogManager log = _dnsServer.LogManager; if (log != null) log.Write("DNS Server successfully loaded DNS application: " + Path.GetFileName(applicationFolder)); } catch (Exception ex) { LogManager log = _dnsServer.LogManager; if (log != null) log.Write("DNS Server failed to load DNS application: " + Path.GetFileName(applicationFolder) + "\r\n" + ex.ToString()); } }); } } public async Task InstallApplicationAsync(string applicationName, Stream appStream) { foreach (char invalidChar in Path.GetInvalidFileNameChars()) { if (applicationName.Contains(invalidChar)) throw new DnsServerException("The application name contains an invalid character: " + invalidChar); } if (_applications.ContainsKey(applicationName)) throw new DnsServerException("DNS application already exists: " + applicationName); using (ZipArchive appZip = new ZipArchive(appStream, ZipArchiveMode.Read, false, Encoding.UTF8)) { string applicationFolder = Path.Combine(_appsPath, applicationName); if (Directory.Exists(applicationFolder)) Directory.Delete(applicationFolder, true); try { appZip.ExtractToDirectory(applicationFolder, true); return await LoadApplicationAsync(applicationFolder, true); } catch { if (Directory.Exists(applicationFolder)) Directory.Delete(applicationFolder, true); throw; } } } public async Task UpdateApplicationAsync(string applicationName, Stream appStream) { if (!_applications.ContainsKey(applicationName)) throw new DnsServerException("DNS application does not exists: " + applicationName); using (ZipArchive appZip = new ZipArchive(appStream, ZipArchiveMode.Read, false, Encoding.UTF8)) { UnloadApplication(applicationName); string applicationFolder = Path.Combine(_appsPath, applicationName); foreach (ZipArchiveEntry entry in appZip.Entries) { string entryPath = entry.FullName; if (Path.DirectorySeparatorChar != '/') entryPath = entryPath.Replace('/', '\\'); string filePath = Path.Combine(applicationFolder, entryPath); if ((entry.Name == "dnsApp.config") && File.Exists(filePath)) continue; //avoid overwriting existing config file Directory.CreateDirectory(Path.GetDirectoryName(filePath)); entry.ExtractToFile(filePath, true); } return await LoadApplicationAsync(applicationFolder, true); } } public void UninstallApplication(string applicationName) { if (_applications.TryRemove(applicationName, out DnsApplication app)) { RefreshAppObjectLists(); app.Dispose(); if (Directory.Exists(app.DnsServer.ApplicationFolder)) Directory.Delete(app.DnsServer.ApplicationFolder, true); } } #endregion #region properties public IReadOnlyDictionary Applications { get { return _applications; } } public IReadOnlyList DnsRequestControllers { get { return _dnsRequestControllers; } } public IReadOnlyList DnsAuthoritativeRequestHandlers { get { return _dnsAuthoritativeRequestHandlers; } } public IReadOnlyList DnsRequestBlockingHandlers { get { return _dnsRequestBlockingHandlers; } } public IReadOnlyList DnsQueryLoggers { get { return _dnsQueryLoggers; } } public IReadOnlyList DnsPostProcessors { get { return _dnsPostProcessors; } } #endregion } }