Engine.cs 6.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200
  1. using System.Formats.Asn1;
  2. using System.IO;
  3. using System.Reflection;
  4. using System.Security.Cryptography.X509Certificates;
  5. using InABox.Clients;
  6. using InABox.Core;
  7. using InABox.IPC;
  8. using InABox.Logging;
  9. using InABox.Rpc;
  10. using PRSServer;
  11. namespace PRSServices;
  12. public interface IEngine
  13. {
  14. string ServiceName { get; set; }
  15. string Version { get; set; }
  16. void Run();
  17. void Stop();
  18. void Configure(Server settings);
  19. PortStatus[] PortStatusList();
  20. }
  21. public abstract class Engine<TProperties> : IEngine where TProperties : ServerProperties
  22. {
  23. private RpcServerPipeTransport _enginemanager;
  24. protected RpcClientPipeTransport? Transport;
  25. public TProperties Properties { get; private set; }
  26. public abstract void Run();
  27. public abstract void Stop();
  28. public virtual PortStatus[] PortStatusList()
  29. {
  30. return new PortStatus[] { };
  31. }
  32. public string ServiceName { get; set; }
  33. public string Version { get; set; }
  34. protected string AppDataFolder { get; set; }
  35. public virtual void Configure(Server server)
  36. {
  37. Properties = server.Properties as TProperties;
  38. AppDataFolder = GetPath(server.Key);
  39. MainLogger.AddLogger(new LogFileLogger(AppDataFolder));
  40. MainLogger.AddLogger(new NamedPipeLogger(server.Key));
  41. _enginemanager = new RpcServerPipeTransport($"{ServiceName}M");
  42. _enginemanager.AddHandler<IEngine, PortStatusCommand, PortStatusParameters, PortStatusResult>(new PortStatusHandler(this));
  43. _enginemanager.AfterMessage += (transport, args) => MainLogger.Send(LogType.Information,"",$"Engine Manager Message: {args.Message?.Command}", Guid.Empty);
  44. _enginemanager.Start();
  45. }
  46. private bool _connecting = false;
  47. protected void CheckConnection()
  48. {
  49. if (_connecting)
  50. {
  51. return;
  52. }
  53. _connecting = true;
  54. // Wait for server connection
  55. while (_connecting)
  56. {
  57. Logger.Send(LogType.Information, "", "Connecting to Database server...");
  58. if (Client.Ping())
  59. _connecting = false;
  60. else
  61. {
  62. Logger.Send(LogType.Error, "", "Database server unavailable. Trying again in 30 seconds...");
  63. Task.Delay(30_000).Wait();
  64. }
  65. }
  66. ClientFactory.SetBypass();
  67. return;
  68. }
  69. /// <summary>
  70. /// Initialise the client to the database server, and set up reconnection loop.
  71. /// </summary>
  72. protected void InitialiseConnection(string serverKey, Platform clientPlatform)
  73. {
  74. Transport = new RpcClientPipeTransport(DatabaseServerProperties.GetPipeName(serverKey, true));
  75. ClientFactory.SetClientType(typeof(RpcClient<>), clientPlatform, Version, Transport);
  76. Transport.OnClose += Transport_OnClose;
  77. CheckConnection();
  78. }
  79. private void Transport_OnClose(IRpcTransport transport, RpcTransportCloseArgs e)
  80. {
  81. // Try to reconnect when lost connection.
  82. Logger.Send(LogType.Error, "", "Database server connection lost.");
  83. CheckConnection();
  84. }
  85. public static string GetPath(string key)
  86. {
  87. if (Assembly.GetEntryAssembly() != null)
  88. return Path.Combine(
  89. Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData),
  90. Path.GetFileNameWithoutExtension(Assembly.GetEntryAssembly().Location),
  91. key
  92. );
  93. return Path.Combine(
  94. Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData),
  95. Path.GetFileNameWithoutExtension(Assembly.GetExecutingAssembly().Location),
  96. key
  97. );
  98. }
  99. public static X509Certificate2? LoadCertificate(string filename)
  100. {
  101. if (!String.IsNullOrWhiteSpace(filename))
  102. {
  103. Logger.Send(LogType.Information, "", $"Certificate FileName is {filename}");
  104. if (File.Exists(filename))
  105. {
  106. Logger.Send(LogType.Information, "", "Certificate found; verifying HTTPS Certificate");
  107. try
  108. {
  109. var certificate = new X509Certificate2(filename);
  110. if (certificate.NotAfter > DateTime.Now)
  111. {
  112. var names = GetDnsNames(certificate);
  113. Logger.Send(LogType.Information, "", $"Certificate valid for {string.Join(',', names)}");
  114. return certificate;
  115. }
  116. else
  117. {
  118. Logger.Send(LogType.Error, "", "HTTPS Certificate has expired, using HTTP instead");
  119. }
  120. }
  121. catch (Exception)
  122. {
  123. Logger.Send(LogType.Error, "", "Error validating HTTPS Certificate, using HTTP instead");
  124. }
  125. }
  126. else
  127. Logger.Send(LogType.Error, "", "Certificate File does not exist!");
  128. }
  129. return null;
  130. }
  131. public static IEnumerable<string> GetDnsNames(X509Certificate2 certificate)
  132. {
  133. yield return certificate.GetNameInfo(X509NameType.DnsName, false);
  134. foreach (var name in DnsAlternateNames(certificate))
  135. yield return name;
  136. }
  137. public static IEnumerable<string> DnsAlternateNames(X509Certificate2 certificate)
  138. {
  139. // Adapted from https://stackoverflow.com/questions/16698307/how-do-you-parse-the-subject-alternate-names-from-an-x509certificate2/59382929#59382929
  140. // OID for SubjectAlternativeName X509 extension.
  141. const string SAN_OID = "2.5.29.17";
  142. var extension = certificate.Extensions[SAN_OID];
  143. if (extension is null) yield break;
  144. // Tag value "2" is defined by:
  145. //
  146. // dNSName [2] IA5String,
  147. //
  148. // in: https://datatracker.ietf.org/doc/html/rfc5280#section-4.2.1.6
  149. var dnsNameTag = new Asn1Tag(TagClass.ContextSpecific, tagValue: 2, isConstructed: false);
  150. var asnReader = new AsnReader(extension.RawData, AsnEncodingRules.BER);
  151. var sequenceReader = asnReader.ReadSequence(Asn1Tag.Sequence);
  152. while (sequenceReader.HasData)
  153. {
  154. var tag = sequenceReader.PeekTag();
  155. if(tag != dnsNameTag)
  156. {
  157. sequenceReader.ReadEncodedValue();
  158. continue;
  159. }
  160. var dnsName = sequenceReader.ReadCharacterString(UniversalTagNumber.IA5String, dnsNameTag);
  161. yield return dnsName;
  162. }
  163. }
  164. }