InternalServer.cs 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351
  1. using H.Pipes;
  2. using H.Pipes.AccessControl;
  3. using H.Pipes.Args;
  4. using InABox.Core;
  5. using InABox.IPC;
  6. using InABox.Rpc;
  7. using System;
  8. using System.Collections.Concurrent;
  9. using System.Collections.Generic;
  10. using System.IO.Pipes;
  11. using System.Linq;
  12. using System.Security.Principal;
  13. using System.Text;
  14. using System.Threading.Tasks;
  15. namespace InABox.Server;
  16. internal class InternalServerSession : IRpcSession
  17. {
  18. public Guid ID { get; set; }
  19. public Platform Platform { get; set; }
  20. public string? Version { get; set; }
  21. public string UserID { get; set; }
  22. public Guid UserGuid { get; set; }
  23. public PipeConnection<RpcMessage?> Connection { get; set; }
  24. }
  25. /// <summary>
  26. /// This is the main server the database engine runs, which the proxies connect to.
  27. /// </summary>
  28. public class InternalServer : IPusher, IRpcServer
  29. {
  30. private PipeServer<RpcMessage?> _transport;
  31. public event LogEvent? OnLog;
  32. public bool IsSecure() => false;
  33. public InternalServer(string name)
  34. {
  35. _transport = new PipeServer<RpcMessage?>(name);
  36. #if WINDOWS
  37. SetPipeSecurity();
  38. #endif
  39. _transport.ClientConnected += Transport_OnConnected;
  40. _transport.ClientDisconnected += Transport_OnDisconnected;
  41. _transport.MessageReceived += Transport_OnMessage;
  42. _transport.ExceptionOccurred += Transport_OnException;
  43. AddHandler(new OpenSessionHandler(this));
  44. AddHandler(new CloseSessionHandler(this));
  45. AddHandler(new RpcPingHandler(this));
  46. AddHandler(new RpcInfoHandler(this));
  47. AddHandler(new RpcValidateHandler(this));
  48. AddHandler(new RpcCheck2FAHandler(this));
  49. AddHandler(new RpcQueryHandler(this));
  50. AddHandler(new RpcSaveHandler(this));
  51. AddHandler(new RpcDeleteHandler(this));
  52. AddHandler(new RpcVersionHandler(this));
  53. AddHandler(new RpcInstallerHandler(this));
  54. AddHandler(new RpcReleaseNotesHandler(this));
  55. }
  56. #region Handlers
  57. private Dictionary<string, IRpcCommandHandler> _handlers = new Dictionary<string, IRpcCommandHandler>();
  58. public void AddHandler<TSender, TCommand, TProperties, TResult>(RpcCommandHandler<TSender, TCommand, TProperties, TResult> handler)
  59. where TSender : class
  60. where TCommand : IRpcCommand<TProperties, TResult>
  61. where TProperties : IRpcCommandParameters, new()
  62. where TResult : IRpcCommandResult, new()
  63. {
  64. _handlers[typeof(TCommand).Name] = handler;
  65. }
  66. #endregion
  67. #region Sessions
  68. private ConcurrentDictionary<Guid, InternalServerSession> _sessions = new ConcurrentDictionary<Guid, InternalServerSession>();
  69. private class OpenSessionHandler : RpcCommandHandler<InternalServer, OpenSessionCommand, OpenSessionParameters, OpenSessionResult>
  70. {
  71. public OpenSessionHandler(InternalServer server) : base(server)
  72. {
  73. }
  74. protected override OpenSessionResult Execute(IRpcSession session, OpenSessionParameters parameters)
  75. {
  76. return new OpenSessionResult
  77. {
  78. SessionID = session.ID
  79. };
  80. }
  81. }
  82. private class CloseSessionHandler : RpcCommandHandler<InternalServer, CloseSessionCommand, CloseSessionParameters, CloseSessionResult>
  83. {
  84. public CloseSessionHandler(InternalServer server) : base(server)
  85. {
  86. }
  87. protected override CloseSessionResult Execute(IRpcSession session, CloseSessionParameters parameters)
  88. {
  89. Sender.DeleteSession(session.ID);
  90. return new CloseSessionResult();
  91. }
  92. }
  93. private InternalServerSession GetSession(Guid sessionID, PipeConnection<RpcMessage?> connection)
  94. {
  95. if(sessionID == Guid.Empty)
  96. {
  97. sessionID = Guid.NewGuid();
  98. }
  99. if(!_sessions.TryGetValue(sessionID, out var session))
  100. {
  101. session = new InternalServerSession
  102. {
  103. ID = sessionID,
  104. Connection = connection
  105. };
  106. _sessions.TryAdd(sessionID, session);
  107. }
  108. return session;
  109. }
  110. private InternalServerSession? DeleteSession(Guid sessionID)
  111. {
  112. _sessions.Remove(sessionID, out InternalServerSession? session);
  113. return session;
  114. }
  115. #endregion
  116. #region Transport Stuff
  117. private void SetPipeSecurity()
  118. {
  119. #pragma warning disable CA1416
  120. var pipeSecurity = new PipeSecurity();
  121. pipeSecurity.AddAccessRule(new PipeAccessRule(new SecurityIdentifier(WellKnownSidType.LocalSid, null), PipeAccessRights.ReadWrite, System.Security.AccessControl.AccessControlType.Allow));
  122. pipeSecurity.AddAccessRule(new PipeAccessRule(new SecurityIdentifier(WellKnownSidType.LocalServiceSid, null), PipeAccessRights.ReadWrite, System.Security.AccessControl.AccessControlType.Allow));
  123. pipeSecurity.AddAccessRule(new PipeAccessRule(new SecurityIdentifier(WellKnownSidType.LocalSystemSid, null), PipeAccessRights.ReadWrite, System.Security.AccessControl.AccessControlType.Allow));
  124. _transport.SetPipeSecurity(pipeSecurity);
  125. #pragma warning restore CA1416
  126. }
  127. public void Start()
  128. {
  129. _transport.StartAsync().Wait();
  130. }
  131. public void Stop()
  132. {
  133. _transport.StopAsync().Wait();
  134. }
  135. private void Transport_OnConnected(object? sender, H.Pipes.Args.ConnectionEventArgs<RpcMessage?> e)
  136. {
  137. OnLog?.Invoke(LogType.Information, "", $"Client Connected");
  138. }
  139. private void Transport_OnMessage(object? sender, H.Pipes.Args.ConnectionMessageEventArgs<RpcMessage?> e)
  140. {
  141. Task.Run(() =>
  142. {
  143. var response = DoMessage(e.Connection, e.Message);
  144. e.Connection.WriteAsync(response);
  145. });
  146. }
  147. private void Transport_OnDisconnected(object? sender, H.Pipes.Args.ConnectionEventArgs<RpcMessage?> e)
  148. {
  149. OnLog?.Invoke(LogType.Information, "", $"Client Disconnected");
  150. foreach(var session in _sessions.Where(x => x.Value.Connection == e.Connection))
  151. {
  152. DeleteSession(session.Key);
  153. }
  154. e.Connection.DisposeAsync();
  155. }
  156. private void Transport_OnException(object? sender, H.Pipes.Args.ExceptionEventArgs e)
  157. {
  158. DoException(null, e.Exception);
  159. }
  160. protected void DoException(IRpcSession? session, Exception e)
  161. {
  162. if ((session?.ID ?? Guid.Empty) != Guid.Empty)
  163. OnLog?.Invoke(LogType.Error, $"", $"Exception Occurred in {session?.ID}: {e.Message}");
  164. }
  165. #endregion
  166. /// <summary>
  167. /// Handle a message from a client.
  168. /// </summary>
  169. /// <param name="connection">The client connection.</param>
  170. /// <param name="message">The message to be handled.</param>
  171. /// <returns>The response to be sent back to the client.</returns>
  172. public RpcMessage? DoMessage(PipeConnection<RpcMessage?>? connection, RpcMessage? message)
  173. {
  174. if(message is null)
  175. {
  176. DoException(null, new Exception("NULL Message Received"));
  177. return null;
  178. }
  179. if(connection is null)
  180. {
  181. DoException(null, new Exception("NULL connection"));
  182. return null;
  183. }
  184. var response = new RpcMessage() { Id = message.Id, Command = message.Command };
  185. try
  186. {
  187. var data = Serialization.ReadBinary<InternalServerMessage>(message.Payload, BinarySerializationSettings.Latest);
  188. var session = GetSession(data.Session, connection);
  189. response = new RpcMessage() { Id = message.Id, Command = message.Command };
  190. if (_handlers.TryGetValue(message.Command, out var command))
  191. {
  192. try
  193. {
  194. response.Payload = command.Execute(session, data.Payload);
  195. }
  196. catch (RpcException err)
  197. {
  198. response.Payload = Encoding.UTF8.GetBytes(err.Message);
  199. response.Error = err.Error;
  200. }
  201. }
  202. else
  203. {
  204. DoException(session, new Exception("Command Not Found"));
  205. response.Error = RpcError.COMMANDNOTFOUND;
  206. }
  207. }
  208. catch(Exception e)
  209. {
  210. DoException(null, e);
  211. response.Payload = Encoding.UTF8.GetBytes(e.Message);
  212. response.Error = RpcError.SERVERERROR;
  213. }
  214. return response;
  215. }
  216. /// <summary>
  217. /// Send a message to a particular client connection.
  218. /// </summary>
  219. /// <param name="connection">The connection to send to.</param>
  220. /// <param name="message">The message to send.</param>
  221. public void Send(PipeConnection<RpcMessage?> connection, RpcMessage message)
  222. {
  223. connection.WriteAsync(message);
  224. }
  225. #region Pusher Stuff
  226. public void PushToAll<TPush>(TPush push) where TPush : BaseObject
  227. {
  228. var internalMessage = new InternalServerMessage
  229. {
  230. Session = Guid.Empty,
  231. Payload = RpcPush.Create(push).WriteBinary(BinarySerializationSettings.Latest)
  232. };
  233. var message = new RpcMessage(Guid.NewGuid(), "Push", internalMessage.WriteBinary(BinarySerializationSettings.Latest));
  234. foreach (var connection in _transport.ConnectedClients)
  235. {
  236. Send(connection, message);
  237. }
  238. }
  239. public void PushToSession<TPush>(Guid session, TPush push) where TPush : BaseObject
  240. {
  241. var internalMessage = new InternalServerMessage
  242. {
  243. Session = session,
  244. Payload = RpcPush.Create(push).WriteBinary(BinarySerializationSettings.Latest)
  245. };
  246. var message = new RpcMessage(Guid.NewGuid(), "Push", internalMessage.WriteBinary(BinarySerializationSettings.Latest));
  247. var sessionConnection = _sessions.FirstOrDefault(x => x.Value.ID == session).Value.Connection;
  248. if(sessionConnection is not null)
  249. {
  250. Send(sessionConnection, message);
  251. }
  252. }
  253. public void PushToSession(Guid session, Type TPush, BaseObject push)
  254. {
  255. var internalMessage = new InternalServerMessage
  256. {
  257. Session = session,
  258. Payload = new RpcPush
  259. {
  260. Object = push,
  261. Type = TPush
  262. }.WriteBinary(BinarySerializationSettings.Latest)
  263. };
  264. var message = new RpcMessage(Guid.NewGuid(), "Push", internalMessage.WriteBinary(BinarySerializationSettings.Latest));
  265. var sessionConnection = _sessions.FirstOrDefault(x => x.Value.ID == session).Value.Connection;
  266. if (sessionConnection is not null)
  267. {
  268. Send(sessionConnection, message);
  269. }
  270. }
  271. public IEnumerable<Guid> GetUserSessions(Guid user) =>
  272. _sessions.Values.Where(x => x.UserGuid == user).Select(x => x.ID);
  273. public IEnumerable<Guid> GetSessions(Platform platform) =>
  274. _sessions.Values.Where(x => x.Platform == platform).Select(x => x.ID);
  275. #endregion
  276. public void Dispose()
  277. {
  278. _transport.DisposeAsync().AsTask().Wait();
  279. }
  280. ~InternalServer()
  281. {
  282. Dispose();
  283. }
  284. }