using H.Pipes; using H.Pipes.AccessControl; using H.Pipes.Args; using InABox.Core; using InABox.IPC; using InABox.Rpc; using System; using System.Collections.Concurrent; using System.Collections.Generic; using System.IO.Pipes; using System.Linq; using System.Security.Principal; using System.Text; using System.Threading.Tasks; namespace InABox.Server; internal class InternalServerSession : IRpcSession { public Guid ID { get; set; } public Platform Platform { get; set; } public string? Version { get; set; } public string UserID { get; set; } public Guid UserGuid { get; set; } public PipeConnection Connection { get; set; } } /// /// This is the main server the database engine runs, which the proxies connect to. /// public class InternalServer : IPusher, IRpcServer { private PipeServer _transport; public event LogEvent? OnLog; public bool IsSecure() => false; public InternalServer(string name) { _transport = new PipeServer(name); #if WINDOWS SetPipeSecurity(); #endif _transport.ClientConnected += Transport_OnConnected; _transport.ClientDisconnected += Transport_OnDisconnected; _transport.MessageReceived += Transport_OnMessage; _transport.ExceptionOccurred += Transport_OnException; AddHandler(new OpenSessionHandler(this)); AddHandler(new CloseSessionHandler(this)); AddHandler(new RpcPingHandler(this)); AddHandler(new RpcInfoHandler(this)); AddHandler(new RpcValidateHandler(this)); AddHandler(new RpcCheck2FAHandler(this)); AddHandler(new RpcQueryHandler(this)); AddHandler(new RpcSaveHandler(this)); AddHandler(new RpcDeleteHandler(this)); AddHandler(new RpcVersionHandler(this)); AddHandler(new RpcInstallerHandler(this)); AddHandler(new RpcReleaseNotesHandler(this)); } #region Handlers private Dictionary _handlers = new Dictionary(); public void AddHandler(RpcCommandHandler handler) where TSender : class where TCommand : IRpcCommand where TProperties : IRpcCommandParameters, new() where TResult : IRpcCommandResult, new() { _handlers[typeof(TCommand).Name] = handler; } #endregion #region Sessions private ConcurrentDictionary _sessions = new ConcurrentDictionary(); private class OpenSessionHandler : RpcCommandHandler { public OpenSessionHandler(InternalServer server) : base(server) { } protected override OpenSessionResult Execute(IRpcSession session, OpenSessionParameters parameters) { return new OpenSessionResult { SessionID = session.ID }; } } private class CloseSessionHandler : RpcCommandHandler { public CloseSessionHandler(InternalServer server) : base(server) { } protected override CloseSessionResult Execute(IRpcSession session, CloseSessionParameters parameters) { Sender.DeleteSession(session.ID); return new CloseSessionResult(); } } private InternalServerSession GetSession(Guid sessionID, PipeConnection connection) { if(sessionID == Guid.Empty) { sessionID = Guid.NewGuid(); } if(!_sessions.TryGetValue(sessionID, out var session)) { session = new InternalServerSession { ID = sessionID, Connection = connection }; _sessions.TryAdd(sessionID, session); } return session; } private InternalServerSession? DeleteSession(Guid sessionID) { _sessions.Remove(sessionID, out InternalServerSession? session); return session; } #endregion #region Transport Stuff private void SetPipeSecurity() { #pragma warning disable CA1416 var pipeSecurity = new PipeSecurity(); pipeSecurity.AddAccessRule(new PipeAccessRule(new SecurityIdentifier(WellKnownSidType.LocalSid, null), PipeAccessRights.ReadWrite, System.Security.AccessControl.AccessControlType.Allow)); pipeSecurity.AddAccessRule(new PipeAccessRule(new SecurityIdentifier(WellKnownSidType.LocalServiceSid, null), PipeAccessRights.ReadWrite, System.Security.AccessControl.AccessControlType.Allow)); pipeSecurity.AddAccessRule(new PipeAccessRule(new SecurityIdentifier(WellKnownSidType.LocalSystemSid, null), PipeAccessRights.ReadWrite, System.Security.AccessControl.AccessControlType.Allow)); _transport.SetPipeSecurity(pipeSecurity); #pragma warning restore CA1416 } public void Start() { _transport.StartAsync().Wait(); } public void Stop() { _transport.StopAsync().Wait(); } private void Transport_OnConnected(object? sender, H.Pipes.Args.ConnectionEventArgs e) { OnLog?.Invoke(LogType.Information, "", $"Client Connected"); } private void Transport_OnMessage(object? sender, H.Pipes.Args.ConnectionMessageEventArgs e) { Task.Run(() => { var response = DoMessage(e.Connection, e.Message); e.Connection.WriteAsync(response); }); } private void Transport_OnDisconnected(object? sender, H.Pipes.Args.ConnectionEventArgs e) { OnLog?.Invoke(LogType.Information, "", $"Client Disconnected"); foreach(var session in _sessions.Where(x => x.Value.Connection == e.Connection)) { DeleteSession(session.Key); } e.Connection.DisposeAsync(); } private void Transport_OnException(object? sender, H.Pipes.Args.ExceptionEventArgs e) { DoException(null, e.Exception); } protected void DoException(IRpcSession? session, Exception e) { if ((session?.ID ?? Guid.Empty) != Guid.Empty) OnLog?.Invoke(LogType.Error, $"", $"Exception Occurred in {session?.ID}: {e.Message}"); } #endregion /// /// Handle a message from a client. /// /// The client connection. /// The message to be handled. /// The response to be sent back to the client. public RpcMessage? DoMessage(PipeConnection? connection, RpcMessage? message) { if(message is null) { DoException(null, new Exception("NULL Message Received")); return null; } if(connection is null) { DoException(null, new Exception("NULL connection")); return null; } var response = new RpcMessage() { Id = message.Id, Command = message.Command }; try { var data = Serialization.ReadBinary(message.Payload, BinarySerializationSettings.Latest); var session = GetSession(data.Session, connection); response = new RpcMessage() { Id = message.Id, Command = message.Command }; if (_handlers.TryGetValue(message.Command, out var command)) { try { response.Payload = command.Execute(session, data.Payload); } catch (RpcException err) { response.Payload = Encoding.UTF8.GetBytes(err.Message); response.Error = err.Error; } } else { DoException(session, new Exception("Command Not Found")); response.Error = RpcError.COMMANDNOTFOUND; } } catch(Exception e) { DoException(null, e); response.Payload = Encoding.UTF8.GetBytes(e.Message); response.Error = RpcError.SERVERERROR; } return response; } /// /// Send a message to a particular client connection. /// /// The connection to send to. /// The message to send. public void Send(PipeConnection connection, RpcMessage message) { connection.WriteAsync(message); } #region Pusher Stuff public void PushToAll(TPush push) where TPush : BaseObject { var internalMessage = new InternalServerMessage { Session = Guid.Empty, Payload = RpcPush.Create(push).WriteBinary(BinarySerializationSettings.Latest) }; var message = new RpcMessage(Guid.NewGuid(), "Push", internalMessage.WriteBinary(BinarySerializationSettings.Latest)); foreach (var connection in _transport.ConnectedClients) { Send(connection, message); } } public void PushToSession(Guid session, TPush push) where TPush : BaseObject { var internalMessage = new InternalServerMessage { Session = session, Payload = RpcPush.Create(push).WriteBinary(BinarySerializationSettings.Latest) }; var message = new RpcMessage(Guid.NewGuid(), "Push", internalMessage.WriteBinary(BinarySerializationSettings.Latest)); var sessionConnection = _sessions.FirstOrDefault(x => x.Value.ID == session).Value.Connection; if(sessionConnection is not null) { Send(sessionConnection, message); } } public void PushToSession(Guid session, Type TPush, BaseObject push) { var internalMessage = new InternalServerMessage { Session = session, Payload = new RpcPush { Object = push, Type = TPush }.WriteBinary(BinarySerializationSettings.Latest) }; var message = new RpcMessage(Guid.NewGuid(), "Push", internalMessage.WriteBinary(BinarySerializationSettings.Latest)); var sessionConnection = _sessions.FirstOrDefault(x => x.Value.ID == session).Value.Connection; if (sessionConnection is not null) { Send(sessionConnection, message); } } public IEnumerable GetUserSessions(Guid user) => _sessions.Values.Where(x => x.UserGuid == user).Select(x => x.ID); public IEnumerable GetSessions(Platform platform) => _sessions.Values.Where(x => x.Platform == platform).Select(x => x.ID); #endregion public void Dispose() { _transport.DisposeAsync().AsTask().Wait(); } ~InternalServer() { Dispose(); } }