Engine.cs 6.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195
  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}");
  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 (!Client.Ping())
  56. {
  57. Logger.Send(LogType.Error, "", "Database server unavailable. Trying again in 30 seconds...");
  58. Task.Delay(30_000).Wait();
  59. Logger.Send(LogType.Information, "", "Retrying connection...");
  60. }
  61. _connecting = false;
  62. ClientFactory.SetBypass();
  63. return;
  64. }
  65. /// <summary>
  66. /// Initialise the client to the database server, and set up reconnection loop.
  67. /// </summary>
  68. protected void InitialiseConnection(string serverKey, Platform clientPlatform)
  69. {
  70. Transport = new RpcClientPipeTransport(DatabaseServerProperties.GetPipeName(serverKey, true));
  71. ClientFactory.SetClientType(typeof(RpcClient<>), clientPlatform, Version, Transport);
  72. Transport.OnClose += Transport_OnClose;
  73. CheckConnection();
  74. }
  75. private void Transport_OnClose(IRpcTransport transport, RpcTransportCloseArgs e)
  76. {
  77. // Try to reconnect when lost connection.
  78. CheckConnection();
  79. }
  80. public static string GetPath(string key)
  81. {
  82. if (Assembly.GetEntryAssembly() != null)
  83. return Path.Combine(
  84. Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData),
  85. Path.GetFileNameWithoutExtension(Assembly.GetEntryAssembly().Location),
  86. key
  87. );
  88. return Path.Combine(
  89. Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData),
  90. Path.GetFileNameWithoutExtension(Assembly.GetExecutingAssembly().Location),
  91. key
  92. );
  93. }
  94. public static X509Certificate2? LoadCertificate(string filename)
  95. {
  96. if (!String.IsNullOrWhiteSpace(filename))
  97. {
  98. Logger.Send(LogType.Information, "", "Certificate FileName is {0}", filename);
  99. if (File.Exists(filename))
  100. {
  101. Logger.Send(LogType.Information, "", "Certificate found; verifying HTTPS Certificate");
  102. try
  103. {
  104. var certificate = new X509Certificate2(filename);
  105. if (certificate.NotAfter > DateTime.Now)
  106. {
  107. var names = GetDnsNames(certificate);
  108. Logger.Send(LogType.Information, "", $"Certificate valid for {string.Join(',', names)}");
  109. return certificate;
  110. }
  111. else
  112. {
  113. Logger.Send(LogType.Error, "", "HTTPS Certificate has expired, using HTTP instead");
  114. }
  115. }
  116. catch (Exception)
  117. {
  118. Logger.Send(LogType.Error, "", "Error validating HTTPS Certificate, using HTTP instead");
  119. }
  120. }
  121. else
  122. Logger.Send(LogType.Error, "", "Certificate File does not exist!");
  123. }
  124. return null;
  125. }
  126. public static IEnumerable<string> GetDnsNames(X509Certificate2 certificate)
  127. {
  128. yield return certificate.GetNameInfo(X509NameType.DnsName, false);
  129. foreach (var name in DnsAlternateNames(certificate))
  130. yield return name;
  131. }
  132. public static IEnumerable<string> DnsAlternateNames(X509Certificate2 certificate)
  133. {
  134. // Adapted from https://stackoverflow.com/questions/16698307/how-do-you-parse-the-subject-alternate-names-from-an-x509certificate2/59382929#59382929
  135. // OID for SubjectAlternativeName X509 extension.
  136. const string SAN_OID = "2.5.29.17";
  137. var extension = certificate.Extensions[SAN_OID];
  138. if (extension is null) yield break;
  139. // Tag value "2" is defined by:
  140. //
  141. // dNSName [2] IA5String,
  142. //
  143. // in: https://datatracker.ietf.org/doc/html/rfc5280#section-4.2.1.6
  144. var dnsNameTag = new Asn1Tag(TagClass.ContextSpecific, tagValue: 2, isConstructed: false);
  145. var asnReader = new AsnReader(extension.RawData, AsnEncodingRules.BER);
  146. var sequenceReader = asnReader.ReadSequence(Asn1Tag.Sequence);
  147. while (sequenceReader.HasData)
  148. {
  149. var tag = sequenceReader.PeekTag();
  150. if(tag != dnsNameTag)
  151. {
  152. sequenceReader.ReadEncodedValue();
  153. continue;
  154. }
  155. var dnsName = sequenceReader.ReadCharacterString(UniversalTagNumber.IA5String, dnsNameTag);
  156. yield return dnsName;
  157. }
  158. }
  159. }