Engine.cs 6.8 KB

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