using System.Formats.Asn1; using System.IO; using System.Reflection; using System.Security.Cryptography.X509Certificates; using InABox.Clients; using InABox.Core; using InABox.IPC; using InABox.Logging; using InABox.Rpc; using PRSServer; namespace PRSServices; public interface IEngine { string ServiceName { get; set; } string Version { get; set; } void Run(); void Stop(); void Configure(Server settings); PortStatus[] PortStatusList(); } public abstract class Engine : IEngine where TProperties : ServerProperties { private RpcServerPipeTransport _enginemanager; protected RpcClientPipeTransport? Transport; public TProperties Properties { get; private set; } public abstract void Run(); public abstract void Stop(); public virtual PortStatus[] PortStatusList() { return new PortStatus[] { }; } public string ServiceName { get; set; } public string Version { get; set; } protected string AppDataFolder { get; set; } public virtual void Configure(Server server) { Properties = server.Properties as TProperties; AppDataFolder = GetPath(server.Key); MainLogger.AddLogger(new LogFileLogger(AppDataFolder)); MainLogger.AddLogger(new NamedPipeLogger(server.Key)); _enginemanager = new RpcServerPipeTransport($"{ServiceName}M"); _enginemanager.AddHandler(new PortStatusHandler(this)); _enginemanager.AfterMessage += (transport, args) => MainLogger.Send(LogType.Information,"",$"Engine Manager Message: {args.Message?.Command}", Guid.Empty); _enginemanager.Start(); } private bool _connecting = false; protected void CheckConnection() { if (_connecting) { return; } _connecting = true; // Wait for server connection while (_connecting) { Logger.Send(LogType.Information, "", "Connecting to Database server..."); if (Client.Ping()) _connecting = false; else { Logger.Send(LogType.Error, "", "Database server unavailable. Trying again in 30 seconds..."); Task.Delay(30_000).Wait(); } } ClientFactory.SetBypass(); return; } /// /// Initialise the client to the database server, and set up reconnection loop. /// protected void InitialiseConnection(string serverKey, Platform clientPlatform) { Transport = new RpcClientPipeTransport(DatabaseServerProperties.GetPipeName(serverKey, true)); ClientFactory.SetClientType(typeof(RpcClient<>), clientPlatform, Version, Transport); Transport.OnClose += Transport_OnClose; CheckConnection(); } private void Transport_OnClose(IRpcTransport transport, RpcTransportCloseArgs e) { // Try to reconnect when lost connection. Logger.Send(LogType.Error, "", "Database server connection lost."); CheckConnection(); } public static string GetPath(string key) { if (Assembly.GetEntryAssembly() != null) return Path.Combine( Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData), Path.GetFileNameWithoutExtension(Assembly.GetEntryAssembly().Location), key ); return Path.Combine( Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData), Path.GetFileNameWithoutExtension(Assembly.GetExecutingAssembly().Location), key ); } public static X509Certificate2? LoadCertificate(string filename) { if (!String.IsNullOrWhiteSpace(filename)) { Logger.Send(LogType.Information, "", $"Certificate FileName is {filename}"); if (File.Exists(filename)) { Logger.Send(LogType.Information, "", "Certificate found; verifying HTTPS Certificate"); try { var certificate = new X509Certificate2(filename); if (certificate.NotAfter > DateTime.Now) { var names = GetDnsNames(certificate); Logger.Send(LogType.Information, "", $"Certificate valid for {string.Join(',', names)}"); return certificate; } else { Logger.Send(LogType.Error, "", "HTTPS Certificate has expired, using HTTP instead"); } } catch (Exception) { Logger.Send(LogType.Error, "", "Error validating HTTPS Certificate, using HTTP instead"); } } else Logger.Send(LogType.Error, "", "Certificate File does not exist!"); } return null; } public static IEnumerable GetDnsNames(X509Certificate2 certificate) { yield return certificate.GetNameInfo(X509NameType.DnsName, false); foreach (var name in DnsAlternateNames(certificate)) yield return name; } public static IEnumerable DnsAlternateNames(X509Certificate2 certificate) { // Adapted from https://stackoverflow.com/questions/16698307/how-do-you-parse-the-subject-alternate-names-from-an-x509certificate2/59382929#59382929 // OID for SubjectAlternativeName X509 extension. const string SAN_OID = "2.5.29.17"; var extension = certificate.Extensions[SAN_OID]; if (extension is null) yield break; // Tag value "2" is defined by: // // dNSName [2] IA5String, // // in: https://datatracker.ietf.org/doc/html/rfc5280#section-4.2.1.6 var dnsNameTag = new Asn1Tag(TagClass.ContextSpecific, tagValue: 2, isConstructed: false); var asnReader = new AsnReader(extension.RawData, AsnEncodingRules.BER); var sequenceReader = asnReader.ReadSequence(Asn1Tag.Sequence); while (sequenceReader.HasData) { var tag = sequenceReader.PeekTag(); if(tag != dnsNameTag) { sequenceReader.ReadEncodedValue(); continue; } var dnsName = sequenceReader.ReadCharacterString(UniversalTagNumber.IA5String, dnsNameTag); yield return dnsName; } } }