/*
Technitium DNS Server
Copyright (C) 2022 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.Generic;
using System.IO;
using System.Reflection;
using System.Runtime.Loader;
using System.Threading.Tasks;
namespace DnsServerCore.Dns.Applications
{
public sealed class DnsApplication : IDisposable
{
#region variables
readonly IDnsServer _dnsServer;
readonly string _name;
readonly DnsApplicationAssemblyLoadContext _appContext;
readonly string _description;
readonly Version _version;
readonly IReadOnlyDictionary _dnsApplications;
readonly IReadOnlyDictionary _dnsAppRecordRequestHandlers;
readonly IReadOnlyDictionary _dnsRequestControllers;
readonly IReadOnlyDictionary _dnsAuthoritativeRequestHandlers;
readonly IReadOnlyDictionary _dnsQueryLoggers;
readonly IReadOnlyDictionary _dnsPostProcessors;
#endregion
#region constructor
public DnsApplication(IDnsServer dnsServer, string name)
{
_dnsServer = dnsServer;
_name = name;
_appContext = new DnsApplicationAssemblyLoadContext(_dnsServer.ApplicationFolder);
//load app assemblies
IEnumerable loadedAssemblies = AssemblyLoadContext.Default.Assemblies;
List appAssemblies = new List();
foreach (string dllFile in Directory.GetFiles(_dnsServer.ApplicationFolder, "*.dll", SearchOption.TopDirectoryOnly))
{
string dllFileName = Path.GetFileNameWithoutExtension(dllFile);
bool isLoaded = false;
foreach (Assembly loadedAssembly in loadedAssemblies)
{
if (!string.IsNullOrEmpty(loadedAssembly.Location))
{
if (Path.GetFileNameWithoutExtension(loadedAssembly.Location).Equals(dllFileName, StringComparison.OrdinalIgnoreCase))
{
isLoaded = true;
break;
}
}
else
{
AssemblyName assemblyName = loadedAssembly.GetName();
if ((assemblyName.Name != null) && assemblyName.Name.Equals(dllFileName, StringComparison.OrdinalIgnoreCase))
{
isLoaded = true;
break;
}
}
}
if (isLoaded)
continue;
try
{
string pdbFile = Path.Combine(_dnsServer.ApplicationFolder, Path.GetFileNameWithoutExtension(dllFile) + ".pdb");
if (File.Exists(pdbFile))
{
using (FileStream dllStream = new FileStream(dllFile, FileMode.Open, FileAccess.Read))
{
using (FileStream pdbStream = new FileStream(pdbFile, FileMode.Open, FileAccess.Read))
{
appAssemblies.Add(_appContext.LoadFromStream(dllStream, pdbStream));
}
}
}
else
{
using (FileStream dllStream = new FileStream(dllFile, FileMode.Open, FileAccess.Read))
{
appAssemblies.Add(_appContext.LoadFromStream(dllStream));
}
}
}
catch (Exception ex)
{
_dnsServer.WriteLog(ex);
}
}
//load apps
Dictionary dnsApplications = new Dictionary();
Dictionary dnsAppRecordRequestHandlers = new Dictionary(2);
Dictionary dnsRequestControllers = new Dictionary(1);
Dictionary dnsAuthoritativeRequestHandlers = new Dictionary(1);
Dictionary dnsQueryLoggers = new Dictionary(1);
Dictionary dnsPostProcessors = new Dictionary(1);
Type dnsApplicationInterface = typeof(IDnsApplication);
foreach (Assembly appAssembly in appAssemblies)
{
try
{
foreach (Type classType in appAssembly.ExportedTypes)
{
bool isDnsApp = false;
foreach (Type interfaceType in classType.GetInterfaces())
{
if (interfaceType == dnsApplicationInterface)
{
isDnsApp = true;
break;
}
}
if (isDnsApp)
{
try
{
IDnsApplication app = Activator.CreateInstance(classType) as IDnsApplication;
dnsApplications.Add(classType.FullName, app);
if (app is IDnsAppRecordRequestHandler appRecordHandler)
dnsAppRecordRequestHandlers.Add(classType.FullName, appRecordHandler);
if (app is IDnsRequestController requestController)
dnsRequestControllers.Add(classType.FullName, requestController);
if (app is IDnsAuthoritativeRequestHandler requestHandler)
dnsAuthoritativeRequestHandlers.Add(classType.FullName, requestHandler);
if (app is IDnsQueryLogger logger)
dnsQueryLoggers.Add(classType.FullName, logger);
if (app is IDnsPostProcessor postProcessor)
dnsPostProcessors.Add(classType.FullName, postProcessor);
if (_description is null)
{
AssemblyDescriptionAttribute attribute = appAssembly.GetCustomAttribute();
if (attribute is not null)
_description = attribute.Description.Replace("\\n", "\n");
}
if (_version is null)
_version = appAssembly.GetName().Version;
}
catch (Exception ex)
{
_dnsServer.WriteLog(ex);
}
}
}
}
catch (Exception ex)
{
_dnsServer.WriteLog(ex);
}
}
if (_version is null)
{
if (dnsApplications.Count > 0)
_version = new Version(1, 0);
else
_version = new Version(0, 0);
}
_dnsApplications = dnsApplications;
_dnsAppRecordRequestHandlers = dnsAppRecordRequestHandlers;
_dnsRequestControllers = dnsRequestControllers;
_dnsAuthoritativeRequestHandlers = dnsAuthoritativeRequestHandlers;
_dnsQueryLoggers = dnsQueryLoggers;
_dnsPostProcessors = dnsPostProcessors;
}
#endregion
#region IDisposable
bool _disposed;
private void Dispose(bool disposing)
{
if (_disposed)
return;
if (disposing)
{
if (_dnsApplications is not null)
{
foreach (KeyValuePair app in _dnsApplications)
app.Value.Dispose();
}
if (_appContext != null)
_appContext.Unload();
}
_disposed = true;
}
public void Dispose()
{
Dispose(true);
}
#endregion
#region internal
internal async Task InitializeAsync()
{
string config = await GetConfigAsync();
foreach (KeyValuePair app in _dnsApplications)
{
try
{
await app.Value.InitializeAsync(_dnsServer, config);
}
catch (Exception ex)
{
_dnsServer.WriteLog(ex);
}
}
}
#endregion
#region public
public Task GetConfigAsync()
{
string configFile = Path.Combine(_dnsServer.ApplicationFolder, "dnsApp.config");
if (File.Exists(configFile))
return File.ReadAllTextAsync(configFile);
return Task.FromResult(null);
}
public async Task SetConfigAsync(string config)
{
string configFile = Path.Combine(_dnsServer.ApplicationFolder, "dnsApp.config");
foreach (KeyValuePair app in _dnsApplications)
await app.Value.InitializeAsync(_dnsServer, config);
if (string.IsNullOrEmpty(config))
File.Delete(configFile);
else
await File.WriteAllTextAsync(configFile, config);
}
#endregion
#region properties
public IDnsServer DnsServer
{ get { return _dnsServer; } }
public string Name
{ get { return _name; } }
public string Description
{ get { return _description; } }
public Version Version
{ get { return _version; } }
public IReadOnlyDictionary DnsApplications
{ get { return _dnsApplications; } }
public IReadOnlyDictionary DnsAppRecordRequestHandlers
{ get { return _dnsAppRecordRequestHandlers; } }
public IReadOnlyDictionary DnsRequestControllers
{ get { return _dnsRequestControllers; } }
public IReadOnlyDictionary DnsAuthoritativeRequestHandlers
{ get { return _dnsAuthoritativeRequestHandlers; } }
public IReadOnlyDictionary DnsQueryLoggers
{ get { return _dnsQueryLoggers; } }
public IReadOnlyDictionary DnsPostProcessors
{ get { return _dnsPostProcessors; } }
#endregion
}
}