Engine.cs 6.3 KB

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