using H.Formatters; using H.Pipes; using H.Pipes.AccessControl; using H.Pipes.Args; using InABox.API; using InABox.Clients; using InABox.Core; using InABox.IPC.Shared; using InABox.Server.WebSocket; using System; using System.Collections.Concurrent; using System.Collections.Generic; using System.Diagnostics; using System.IO.Pipes; using System.Linq; using System.Reflection; using System.Security.Principal; using System.Text; using System.Threading.Tasks; namespace Piping { using PipeResponse = PipeRequest; delegate void PipePollEvent(PipeNotifyState.Session session); class PipeNotifyState { public class Session { public PipeConnection Connection { get; } public Guid SessionID { get; } public Platform Platform { get; } public Session(PipeConnection connection, Guid sessionID, Platform platform) { Connection = connection; SessionID = sessionID; Platform = platform; } } public ConcurrentDictionary SessionMap = new(); public event PipePollEvent? OnPoll; public void Poll(Session session) { OnPoll?.Invoke(session); } } class PipeIPCNotifier : Notifier { PipeNotifyState NotifyState { get; set; } public PipeIPCNotifier(PipeNotifyState notifyState) { NotifyState = notifyState; NotifyState.OnPoll += NotifyState_OnPoll; } private void NotifyState_OnPoll(PipeNotifyState.Session session) { Notify.Poll(session.SessionID); } protected override IEnumerable GetSessions(Platform platform) { return NotifyState.SessionMap.Where(x => x.Value.Platform == platform).Select(x => x.Key); } protected override IEnumerable GetUserSessions(Guid userID) { return CredentialsCache.GetUserSessions(userID); } protected override void NotifyAll(TNotification notification) { foreach(var session in NotifyState.SessionMap.Values) { session.Connection.WriteAsync(PipeRequest.Notification(notification)).ContinueWith(task => { if(task.Exception != null) { Logger.Send(LogType.Error, "", $"Error in notification: {CoreUtils.FormatException(task.Exception)}"); } }); } } protected override void NotifySession(Guid sessionID, TNotification notification) { if(NotifyState.SessionMap.TryGetValue(sessionID, out var session)) { session.Connection.WriteAsync(PipeRequest.Notification(notification)).ContinueWith(task => { if (task.Exception != null) { Logger.Send(LogType.Error, "", $"Error in notification: {CoreUtils.FormatException(task.Exception)}"); } }); } } protected override void NotifySession(Guid sessionID, Type TNotification, BaseObject notification) { if (NotifyState.SessionMap.TryGetValue(sessionID, out var session)) { session.Connection.WriteAsync(PipeRequest.Notification(TNotification, notification)).ContinueWith(task => { if (task.Exception != null) { Logger.Send(LogType.Error, "", $"Error in notification: {CoreUtils.FormatException(task.Exception)}"); } }); } } } public class PipeIPCServer : IDisposable { PipeServer Server; PipeNotifyState NotifyState = new(); public PipeIPCServer(string name) { Server = new PipeServer(name); Notify.AddNotifier(new PipeIPCNotifier(NotifyState)); 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)); Server.SetPipeSecurity(pipeSecurity); Server.ClientConnected += Server_ClientConnected; Server.ClientDisconnected += Server_ClientDisconnected; Server.MessageReceived += Server_MessageReceived; Server.ExceptionOccurred += Server_ExceptionOccurred; } private void Server_ExceptionOccurred(object? sender, H.Pipes.Args.ExceptionEventArgs e) { Logger.Send(LogType.Error, "", $"Exception Occurred: {e.Exception.Message}"); } public void Start() { Server.StartAsync().Wait(); } private static List? _persistentRemotable; private static Type? GetEntity(string entityName) { _persistentRemotable ??= CoreUtils.TypeList( e => e.IsSubclassOf(typeof(Entity)) && e.GetInterfaces().Contains(typeof(IRemotable)) && e.GetInterfaces().Contains(typeof(IPersistent))).ToList(); return _persistentRemotable.FirstOrDefault(x => x.Name == entityName); } private static Type? GetResponseType(Method method, string? entityName) { if(entityName != null) { var entityType = GetEntity(entityName); if(entityType != null) { var response = method switch { Method.Query => typeof(QueryResponse<>).MakeGenericType(entityType), Method.Delete => typeof(DeleteResponse<>).MakeGenericType(entityType), Method.MultiDelete => typeof(MultiDeleteResponse<>).MakeGenericType(entityType), Method.Save => typeof(SaveResponse<>).MakeGenericType(entityType), Method.MultiSave => typeof(MultiSaveResponse<>).MakeGenericType(entityType), _ => null }; if (response != null) return response; } } return method switch { Method.QueryMultiple => typeof(MultiQueryResponse), Method.Validate => typeof(ValidateResponse), Method.Check2FA => typeof(Check2FAResponse), _ => null }; } private class RequestData { public ConnectionMessageEventArgs e { get; } public RequestData(ConnectionMessageEventArgs e) { this.e = e; } } private PipeResponse QueryMultiple(PipeRequest request, RequestData data) { var response = RestService.QueryMultiple(request.GetRequest(), true); return request.Respond(response); } private PipeResponse Validate(PipeRequest request, RequestData data) { var validateRequest = request.GetRequest(); var response = RestService.Validate(validateRequest); if(response.Session != Guid.Empty) { var newSession = new PipeNotifyState.Session(data.e.Connection, response.Session, validateRequest.Credentials.Platform); NotifyState.SessionMap[response.Session] = newSession; NotifyState.Poll(newSession); } return request.Respond(response); } private PipeResponse Ping(PipeRequest request, RequestData data) => request.Respond(new PingResponse().Status(StatusCode.OK)); private PipeResponse Info(PipeRequest request, RequestData data) { var response = RestService.Info(request.GetRequest()); return request.Respond(response); } private PipeResponse Check2FA(PipeRequest request, RequestData data) { var response = RestService.Check2FA(request.GetRequest()); return request.Respond(response); } private PipeResponse Query(PipeRequest request, RequestData data) where T : Entity, new() { var response = RestService.List(request.GetRequest>()); return request.Respond(response); } private PipeResponse Save(PipeRequest request, RequestData data) where T : Entity, new() { var response = RestService.Save(request.GetRequest>()); return request.Respond(response); } private PipeResponse MultiSave(PipeRequest request, RequestData data) where T : Entity, new() { var response = RestService.MultiSave(request.GetRequest>()); return request.Respond(response); } private PipeResponse Delete(PipeRequest request, RequestData data) where T : Entity, new() { var response = RestService.Delete(request.GetRequest>()); return request.Respond(response); } private PipeResponse MultiDelete(PipeRequest request, RequestData data) where T : Entity, new() { var response = RestService.MultiDelete(request.GetRequest>()); return request.Respond(response); } private static readonly MethodInfo QueryMethod = GetMethod(nameof(Query)); private static readonly MethodInfo SaveMethod = GetMethod(nameof(Save)); private static readonly MethodInfo MultiSaveMethod = GetMethod(nameof(MultiSave)); private static readonly MethodInfo DeleteMethod = GetMethod(nameof(Delete)); private static readonly MethodInfo MultiDeleteMethod = GetMethod(nameof(MultiDelete)); private static readonly MethodInfo QueryMultipleMethod = GetMethod(nameof(QueryMultiple)); private static readonly MethodInfo ValidateMethod = GetMethod(nameof(Validate)); private static readonly MethodInfo Check2FAMethod = GetMethod(nameof(Check2FA)); private static readonly MethodInfo PingMethod = GetMethod(nameof(Ping)); private static readonly MethodInfo InfoMethod = GetMethod(nameof(Info)); private static MethodInfo GetMethod(string name) => typeof(PipeIPCServer).GetMethod(name, BindingFlags.NonPublic | BindingFlags.Instance) ?? throw new Exception($"Invalid method '{name}'"); private void Server_MessageReceived(object? sender, ConnectionMessageEventArgs e) { Task.Run(() => { var start = DateTime.Now; try { if (e.Message == null) throw new Exception($"Invalid message"); var method = e.Message.Method switch { Method.Query => QueryMethod, Method.QueryMultiple => QueryMultipleMethod, Method.Delete => DeleteMethod, Method.MultiDelete => MultiDeleteMethod, Method.Save => SaveMethod, Method.MultiSave => MultiSaveMethod, Method.Check2FA => Check2FAMethod, Method.Validate => ValidateMethod, Method.Ping => PingMethod, Method.Info => InfoMethod, Method.None or _ => throw new Exception($"Invalid method '{e.Message.Method}'") }; if (e.Message.Type != null) { var entityType = GetEntity(e.Message.Type) ?? throw new Exception($"No entity '{e.Message.Type}'"); method = method.MakeGenericMethod(entityType); } var response = method.Invoke(this, new object[] { e.Message, new RequestData(e) }) as PipeResponse; e.Connection.WriteAsync(response).ContinueWith(task => { if (task.Exception != null) { Logger.Send(LogType.Error, "", $"Error in response: {CoreUtils.FormatException(task.Exception)}"); } }); } catch (Exception err) { Logger.Send(LogType.Error, "", err.Message); if (e.Message != null) { var responseType = GetResponseType(e.Message.Method, e.Message.Type); if (responseType != null) { var response = (Activator.CreateInstance(responseType) as Response)!; response.Status = StatusCode.Error; response.Messages.Add(err.Message); e.Connection.WriteAsync(e.Message.Respond(response)).ContinueWith(task => { if (task.Exception != null) { Logger.Send(LogType.Error, "", $"Error in response: {CoreUtils.FormatException(task.Exception)}"); } }); } } } }); } private void Server_ClientDisconnected(object? sender, H.Pipes.Args.ConnectionEventArgs e) { Logger.Send(LogType.Information, "", "Client Disconnected"); var sessionID = NotifyState.SessionMap.Where(x => x.Value.Connection == e.Connection).FirstOrDefault().Key; NotifyState.SessionMap.TryRemove(sessionID, out var session); e.Connection.DisposeAsync(); } private void Server_ClientConnected(object? sender, H.Pipes.Args.ConnectionEventArgs e) { Logger.Send(LogType.Information, "", "Client Connected"); } public void Dispose() { Server.DisposeAsync().AsTask().Wait(); } ~PipeIPCServer() { Dispose(); } } }